package tzatziki.pdf.emitter; import com.google.common.base.Optional; import com.itextpdf.text.Chunk; import com.itextpdf.text.Element; import com.itextpdf.text.Font; import com.itextpdf.text.Paragraph; import com.itextpdf.text.Phrase; import com.itextpdf.text.Rectangle; import com.itextpdf.text.pdf.PdfPCell; import com.itextpdf.text.pdf.PdfPTable; import gutenberg.itext.AlternateTableRowBackground; import gutenberg.itext.Emitter; import gutenberg.itext.ITextContext; import gutenberg.itext.Styles; import gutenberg.itext.model.RichText; import gutenberg.util.Consumer; import gutenberg.util.KeyValues; import tzatziki.analysis.exec.model.DataTable; import tzatziki.analysis.exec.model.Embedded; import tzatziki.analysis.exec.model.ResultExec; import tzatziki.analysis.exec.model.StepExec; import tzatziki.pdf.model.Steps; import java.util.List; /** * @author <a href="http://twitter.com/aloyer">@aloyer</a> */ public class StepsEmitter implements Emitter<Steps> { public static final String STEP_KEYWORD_FONT = "step-keyword-font"; public static final String STEP_PHRASE_FONT = "step-phrase-font"; public static final String STEP_PARAMETER_FONT = "step-parameter-font"; public static final String STEP_DOCSTRING = "step-docstring"; public static final String STEP_TABLE_CELL = "step-table-cell"; public static final String STEP_TABLE_CELL_HEADER = "step-table-cell-header"; public static final String STEP_TABLE_MODE = "step-table-mode"; private static boolean debugTable = false; private StatusMarker statusMarker = new StatusMarker(); @Override public void emit(Steps stepContainer, ITextContext emitterContext) { PdfPTable steps = new PdfPTable(new float[]{1f, 2f, 22f}); for (StepExec stepExec : stepContainer.steps()) { emitStep(steps, stepExec, emitterContext); } steps.setWidthPercentage(100); steps.setSpacingBefore(5f); steps.setSpacingAfter(5f); steps.setHorizontalAlignment(Element.ALIGN_LEFT); emitterContext.append(steps); } private void emitStep(final PdfPTable steps, StepExec step, ITextContext emitterContext) { KeyValues kvs = emitterContext.keyValues(); Styles styles = kvs.<Styles>getNullable(Styles.class).get(); PdfPCell statusCell = statusCell(step); PdfPCell keywordCell = keywordCell(step, styles); PdfPCell phraseCell = phraseCell(step, styles, emitterContext); steps.addCell(statusCell); steps.addCell(keywordCell); steps.addCell(phraseCell); if (step.hasTable()) { // table added on stepParagraph is not visible... // thus it becomes a direct nested table PdfPTable table = stepDataTable(step.table(), styles); int mode = kvs.getInteger(STEP_TABLE_MODE, 1); if (mode == 1) { steps.addCell(noBorder(colspan(2, new PdfPCell(new Phrase(""))))); steps.addCell(noBorder(new PdfPCell(table))); } else if (mode == 2) { steps.addCell(noBorder(new PdfPCell(new Phrase("")))); steps.addCell(noBorder(colspan(2, new PdfPCell(table)))); } else { steps.addCell(noBorder(colspan(3, new PdfPCell(table)))); } } if (step.hasDocString()) { String docString = step.docString(); Phrase phrase = new Phrase(docString, styles.getFontOrDefault(STEP_DOCSTRING)); steps.addCell(noBorder(colspan(2, new PdfPCell(new Phrase(""))))); steps.addCell(noBorder(new PdfPCell(phrase))); } emitterContext.pushElementConsumer(appendToTable(steps)); try { for (Embedded embedded : step.embeddeds()) { emitterContext.emit(embedded); } } finally { emitterContext.popElementConsumer(); } } private Consumer<Element> appendToTable(final PdfPTable steps) { return new Consumer<Element>() { @Override public void consume(Element element) { PdfPCell cell = new PdfPCell(); cell.addElement(element); steps.addCell(noBorder(colspan(2, new PdfPCell(new Phrase(""))))); steps.addCell(noBorder(cell)); } }; } private PdfPTable stepDataTable(DataTable table, Styles styles) { PdfPTable iTable = new PdfPTable(table.nbColumns()); iTable.setTableEvent(new AlternateTableRowBackground(styles)); int rownum = 0; for (DataTable.Row row : table.rows()) { Font font = null; if (rownum == 0) { Optional<Font> fontOpt = styles.getFont(STEP_TABLE_CELL_HEADER); if (fontOpt.isPresent()) { font = fontOpt.get(); } } if (font == null) font = styles.getFontOrDefault(STEP_TABLE_CELL); for (String value : row.cells()) { PdfPCell cell = new PdfPCell(new Phrase(value, font)); iTable.addCell(noBorder(cell)); } rownum++; } return iTable; } private PdfPCell phraseCell(StepExec step, Styles styles, ITextContext emitterContext) { Paragraph pPhrase = formatStep(step, false, styles, emitterContext); PdfPCell phraseCell = new PdfPCell(pPhrase); phraseCell.setVerticalAlignment(Element.ALIGN_TOP); phraseCell.setHorizontalAlignment(Element.ALIGN_LEFT); phraseCell = noBorder(phraseCell); return phraseCell; } public static Paragraph formatStep(StepExec step, boolean includeKeyword, Styles styles, ITextContext emitterContext) { Font stepKeywordFont = styles.getFontOrDefault(STEP_KEYWORD_FONT); Font stepPhraseFont = styles.getFontOrDefault(STEP_PHRASE_FONT); Font stepParamFont = styles.getFontOrDefault(STEP_PARAMETER_FONT); Paragraph pPhrase = new Paragraph(); if (includeKeyword) { pPhrase.add(new Chunk(step.keyword(), stepKeywordFont)); pPhrase.add(new Chunk(" ")); } if (!step.isMatching()) { pPhrase.add(new Chunk(step.name(), stepPhraseFont)); } else { for (StepExec.Tok tok : step.tokenizeBody()) { Font tokFont = stepPhraseFont; if (tok.param) tokFont = stepParamFont; List<Element> richText = emitterContext.emitButCollectElements(new RichText(tok.value, tokFont)); pPhrase.addAll(richText); } } return pPhrase; } private PdfPCell keywordCell(StepExec step, Styles styles) { Font stepKeywordFont = styles.getFontOrDefault(STEP_KEYWORD_FONT); Paragraph pKeyword = new Paragraph(step.keyword(), stepKeywordFont); PdfPCell keywordCell = new PdfPCell(pKeyword); keywordCell.setVerticalAlignment(Element.ALIGN_TOP); keywordCell.setHorizontalAlignment(Element.ALIGN_RIGHT); keywordCell = noBorder(keywordCell); return keywordCell; } private PdfPCell statusCell(StepExec step) { ResultExec result = step.result(); Phrase statusSymbol; if (result == null) { // this happen when step comes from outline; no status is then attached statusSymbol = new Phrase(""); } else { statusSymbol = new Phrase(statusMarker.statusMarker(result.status())); } PdfPCell statusCell = new PdfPCell(statusSymbol); statusCell.setVerticalAlignment(Element.ALIGN_TOP); statusCell.setHorizontalAlignment(Element.ALIGN_RIGHT); statusCell = noBorder(statusCell); return statusCell; } private static PdfPCell colspan(int colspan, PdfPCell cell) { cell.setColspan(colspan); return cell; } private static PdfPCell noBorder(PdfPCell cell) { if (!debugTable) { cell.setBorder(Rectangle.NO_BORDER); } return cell; } }