package net.sf.jpluck.plucker;
import net.sf.jpluck.palm.PdbOutputStream;
import net.sf.jpluck.plucker.functions.EmbeddedImage;
import net.sf.jpluck.plucker.functions.EmbeddedTable;
import net.sf.jpluck.plucker.functions.Function;
import net.sf.jpluck.plucker.functions.LinkEnd;
import net.sf.jpluck.plucker.functions.LinkFunction;
import net.sf.jpluck.plucker.functions.LinkStart;
import net.sf.jpluck.plucker.functions.Margin;
import net.sf.jpluck.plucker.functions.Newline;
import net.sf.jpluck.plucker.functions.ParagraphLinkStart;
import net.sf.jpluck.plucker.functions.SignificantContent;
import net.sf.jpluck.plucker.functions.TextColor;
import net.sf.jpluck.util.URIUtil;
import java.awt.Color;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
public class Paragraph implements Serializable {
public static final int DEFAULT_SPACING = 2;
transient TextRecord textRecord;
private transient List anchorList = new ArrayList();
private List contentList = new ArrayList();
private transient List uriList = new ArrayList();
private Set resourceSet = new HashSet();
private int size;
private int spacing;
private int written;
Paragraph(int spacing) {
setSpacing(spacing);
}
Paragraph() {
this(0);
}
public String[] getAnchors() {
return (String[]) anchorList.toArray(new String[anchorList.size()]);
}
public boolean isEmpty() {
return (contentList.size() == 0);
}
public int getMaximumSize() {
int size = 0;
for (Iterator iterator = contentList.iterator(); iterator.hasNext();) {
Object obj = iterator.next();
if (obj instanceof Text) {
size += ((Text) obj).getSize();
}
if (obj instanceof EmbeddedImage) {
EmbeddedImage image = (EmbeddedImage) obj;
if ((textRecord.document.resolve(image.getURI()) == null) && (image.getAlt() != null)) {
// Alt text if image is not found
size += (2 + image.getAlt().getSize());
continue;
}
}
if (obj instanceof Function) {
size += (1 + ((Function) obj).getSize()); // preceding NUL + size of the function itself
}
if ((obj instanceof LinkStart || obj instanceof ParagraphLinkStart || obj instanceof LinkEnd)) {
size += (1 + 4); // We have to add 5 bytes for setting the link color.
}
if (obj instanceof ListBullet) {
size += ((ListBullet) obj).getSize();
}
if (obj instanceof OrderedListItem) {
size += ((OrderedListItem) obj).getSize();
}
}
return size;
}
public Resource[] getResources() {
return (Resource[]) resourceSet.toArray(new Resource[resourceSet.size()]);
}
public int getSize() {
return size;
}
public void setSpacing(int spacing) {
if ((spacing < 0) || (spacing > 7)) {
throw new IllegalArgumentException("Invalid paragraph spacing " + spacing);
}
this.spacing = spacing;
}
public int getSpacing() {
return spacing;
}
public Paragraph addAlignCenter() {
return addFunction(Functions.alignCenter());
}
public Paragraph addAlignJustify() {
return addFunction(Functions.alignJustify());
}
public Paragraph addAlignLeft() {
return addFunction(Functions.alignLeft());
}
public Paragraph addAlignRight() {
return addFunction(Functions.alignRight());
}
public void addAnchor(String anchor) {
if (!anchorList.contains("anchor")) {
anchorList.add(anchor);
}
}
public Paragraph addEmbeddedImage(String uri) {
return addFunction(new EmbeddedImage(uri));
}
public Paragraph addEmbeddedImage(String uri, String alt) {
if (alt != null) {
return addFunction(new EmbeddedImage(uri, new Text(alt, textRecord.getOutputEncoding())));
} else {
return addFunction(new EmbeddedImage(uri));
}
}
public Paragraph addFontBold() {
return addFunction(Functions.fontBold());
}
public Paragraph addFontFixedWidth() {
return addFunction(Functions.fontFixedWidth());
}
// Small fonts are not supported by the viewer for now so we leave it out for the time being
/*public Paragraph addFontSmall() {
return addFunction(Functions.fontSmall());
}*/
public Paragraph addFontH1() {
return addFunction(Functions.fontH1());
}
public Paragraph addFontH2() {
return addFunction(Functions.fontH2());
}
public Paragraph addFontH3() {
return addFunction(Functions.fontH3());
}
public Paragraph addFontH4() {
return addFunction(Functions.fontH4());
}
public Paragraph addFontH5() {
return addFunction(Functions.fontH5());
}
public Paragraph addFontH6() {
return addFunction(Functions.fontH6());
}
public Paragraph addFontRegular() {
return addFunction(Functions.fontRegular());
}
public Paragraph addFontSubscript() {
return addFunction(Functions.fontSubscript());
}
public Paragraph addFontSuperscript() {
return addFunction(Functions.fontSuperScript());
}
public Paragraph addFunction(Function function) {
contentList.add(function);
size += function.getSize();
if (function instanceof LinkStart || function instanceof ParagraphLinkStart || function instanceof LinkEnd) {
size += (1 + 4); // We have to add 5 bytes for setting the link color.
}
if (function instanceof LinkStart || function instanceof ParagraphLinkStart) {
String uri = ((LinkFunction) function).getURI();
if (!uriList.contains(uri)) {
uriList.add(uri);
}
}
return this;
}
public Paragraph addHorizontalRuleAbsolute(int width, int height) {
return addFunction(Functions.horizontalRuleAbsolute(width, height));
}
public Paragraph addHorizontalRuleRelative(int width, int height) {
return addFunction(Functions.horizontalRuleRelative(width, height));
}
public Paragraph addItalicEnd() {
return addFunction(Functions.italicEnd());
}
public Paragraph addItalicStart() {
return addFunction(Functions.italicStart());
}
public Paragraph addLinkEnd() {
return addFunction(Functions.linkEnd());
}
public Paragraph addLinkStart(String uri) {
return addFunction(new LinkStart(uri));
}
public Paragraph addLinkStart(String uri, int paragraphNumber) {
return addFunction(new ParagraphLinkStart(uri, paragraphNumber));
}
public Paragraph addListBullet(int margin) {
contentList.add(new ListBullet(margin, textRecord.isForHires()));
return this;
}
public Paragraph addMargin(int left, int right) {
return addFunction(Functions.margin(left, right));
}
public Paragraph addNewline() {
if (contentList.size() > 0) {
Object lastObject = contentList.get(contentList.size() - 1);
if (lastObject instanceof Newline) {
addText(Text.createNonBreakingSpace());
} else if (lastObject instanceof Text) {
Text text = (Text) lastObject;
if (!(text.hasSignificantContent())) {
contentList.remove(text);
size -= text.getSize();
addText(Text.createNonBreakingSpace());
}
}
}
return addFunction(Functions.newline());
}
public Paragraph addOrderedListItem(int margin, int number) {
contentList.add(new OrderedListItem(margin, number));
return this;
}
public Paragraph addPreformattedText(String text) {
return addText(new Text(text, textRecord.outputEncoding), true);
}
public Paragraph addResource(Resource resource) {
if (resource == null) {
throw new NullPointerException("Resource cannot be null.");
}
if (!resourceSet.contains(resource)) {
resourceSet.add(resource);
}
contentList.add(resource);
size += resource.getSize();
return this;
}
public Paragraph addResource(String uri) {
Resource resource = Resource.getResource(uri);
if (resource != null) {
addResource(resource);
} else {
throw new IllegalArgumentException("Resource not found \"" + uri + "\"");
}
return this;
}
public Paragraph addStrikethroughEnd() {
return addFunction(Functions.strikethroughEnd());
}
public Paragraph addStrikethroughStart() {
return addFunction(Functions.strikethroughStart());
}
public Paragraph addTable(String uri) {
return addFunction(new EmbeddedTable(uri));
}
public Paragraph addText(String text) {
return addText(new Text(text, textRecord.outputEncoding), false);
}
public Paragraph addText(Text text, boolean preformatted) {
if (!preformatted) {
if (contentList.size() > 0) {
// Collapse adjacent Text sections.
Object obj = contentList.get(contentList.size() - 1);
if (obj instanceof Text) {
Text prevText = (Text) obj;
size += prevText.add(text);
return this;
}
if (obj instanceof OrderedListItem || obj instanceof ListBullet) {
text.stripLeadingSpace();
}
}
}
if (text.getSize() > 0) {
contentList.add(text);
size += text.getSize();
}
return this;
}
public Paragraph addText(Text text) {
return addText(text, false);
}
public Paragraph addTextColor(int red, int green, int blue) {
return addFunction(Functions.textColor(red, green, blue));
}
public Paragraph addTextColor(Color color) {
return addFunction(Functions.textColor(color));
}
public Paragraph addUnderlineEnd() {
return addFunction(Functions.underlineEnd());
}
public Paragraph addUnderlineStart() {
return addFunction(Functions.underlineStart());
}
public void clear() {
contentList.clear();
size = 0;
}
public boolean containsListItemOnly() {
boolean containsListItem = false;
for (Iterator iterator = contentList.iterator(); iterator.hasNext();) {
Object o = iterator.next();
if (o instanceof Text) {
if (((Text) o).hasSignificantContent()) {
return false;
}
}
if (o instanceof SignificantContent) {
return false;
}
if (o instanceof ListBullet || o instanceof OrderedListItem) {
containsListItem = true;
}
}
return containsListItem;
}
public boolean containsSignificantContent() {
for (Iterator iterator = contentList.iterator(); iterator.hasNext();) {
Object o = iterator.next();
if (o instanceof Text) {
if (((Text) o).hasSignificantContent()) {
return true;
}
}
if (o instanceof SignificantContent) {
return true;
}
}
return false;
}
public void write(PdbOutputStream out) throws IOException {
// Write Paragraph records in Text Record
int prevSize = out.size();
Color linkColor = textRecord.document.getDefaultLinkColor();
if (linkColor == null) {
linkColor = textRecord.getLinkColor();
}
boolean linkColored = false;
boolean linkStarted = true;
for (int i = 0, n = contentList.size(); i < n; i++) {
Object obj = contentList.get(i);
//boolean fullSizeLink = false;
if (obj instanceof Text) {
Text text = (Text) obj;
// Skip insignificant content at the start of the Paragraph.
if (!((i == 0) && !text.hasSignificantContent())) {
text.write(out);
}
} else if (obj instanceof Function) {
Function function = (Function) obj;
if (function instanceof EmbeddedImage) {
EmbeddedImage image = (EmbeddedImage) function;
if (textRecord.document.getLinkableRecord(image.getURI()) == null) {
// If the image is not found in the Document then we try to write the alt text instead.
if ((image.getAlt() != null) && (image.getAlt().getSize() > 0) &&
textRecord.document.isIncludeImageAltText()) {
out.writeString("[");
image.getAlt().write(out);
out.writeString("]");
}
continue;
}
}
if (function instanceof LinkFunction) {
LinkFunction linkFunction = (LinkFunction) function;
linkFunction.setURIResolver(textRecord.document);
}
if (function instanceof LinkEnd && !linkStarted) {
continue;
}
if (function instanceof LinkStart) {
LinkFunction linkFunction = (LinkFunction) function;
boolean brokenLink = !textRecord.document.contains(URIUtil.removeAnchor(linkFunction.getURI()));
if (brokenLink && textRecord.document.isRemoveUnresolvedLinks()) {
linkStarted = false;
continue;
} else {
linkStarted = true;
}
if (textRecord.document.isUseLinkColoring()) {
Color brokenLinkColor = textRecord.document.getUnresolvedLinkColor();
if (brokenLink && (brokenLinkColor != null)) {
// Add a text color function to color the link
TextColor textColor = new TextColor(brokenLinkColor);
// Preceding NUL
out.writeByte(0);
textColor.write(out);
linkColored = true;
} else if (linkColor != null) {
// Add a text color function to color the link
TextColor textColor = new TextColor(linkColor);
// Preceding NUL
out.writeByte(0);
textColor.write(out);
linkColored = true;
}
}
}
if (function instanceof TextColor) {
if (textRecord.document.isUseTextColors()) {
textRecord.currentTextColor = ((TextColor) function).getColor();
} else {
continue;
}
}
out.writeByte(0); // Preceding NUL
function.write(out);
if (function instanceof LinkEnd && linkColored) {
// Preceding NUL
out.writeByte(0);
if (textRecord.currentTextColor != null) {
new TextColor(textRecord.currentTextColor).write(out);
} else {
// Assume black
Functions.TEXT_COLOR_BLACK.write(out);
}
linkColored = false;
}
} else if (obj instanceof Resource) {
((Resource) obj).write(out);
} else if (obj instanceof ListBullet) {
((ListBullet) obj).write(out);
} else if (obj instanceof OrderedListItem) {
((OrderedListItem) obj).write(out);
}
}
out.flush();
written = out.size() - prevSize;
}
String[] getLinkURIs() {
return (String[]) uriList.toArray(new String[uriList.size()]);
}
int getWrittenSize() {
return written;
}
private static class ListBullet implements Serializable {
private byte[] content = new byte[6];
ListBullet(int margin, boolean hires) {
content[0] = (byte) 0x95; // MID-DOT;
content[1] = ' ';
content[2] = 0;
content[3] = Function.SET_MARGIN;
content[4] = (byte) (margin + (6 * (hires? 2 : 1)));
content[5] = 0;
}
public int getSize() {
return content.length;
}
public void write(PdbOutputStream out) throws IOException {
out.write(content);
}
}
private static class OrderedListItem implements Serializable {
private byte[] content;
OrderedListItem(int margin, int number) {
try {
margin += 9;
if (number > 9) {
margin += 5;
}
if (number > 99) {
margin += 5;
}
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdbOutputStream out = new PdbOutputStream(baos);
new Text(number + ". ", "ISO-8859-1").write(out);
out.writeByte(0);
new Margin(margin, 0).write(out);
out.close();
content = baos.toByteArray();
} catch (IOException e) {
// Should not occur
throw new RuntimeException(e);
}
}
public int getSize() {
return content.length;
}
public void write(PdbOutputStream out) throws IOException {
out.write(content);
}
}
}