/* ******************************************************************************
* Copyright (c) 2006-2012 XMind Ltd. and others.
*
* This file is a part of XMind 3. XMind releases 3 and
* above are dual-licensed under the Eclipse Public License (EPL),
* which is available at http://www.eclipse.org/legal/epl-v10.html
* and the GNU Lesser General Public License (LGPL),
* which is available at http://www.gnu.org/licenses/lgpl.html
* See http://www.xmind.net/license.html for details.
*
* Contributors:
* XMind Ltd. - initial API and implementation
*******************************************************************************/
package org.xmind.ui.internal.notes;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Stack;
import org.eclipse.jface.resource.JFaceResources;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.graphics.GlyphMetrics;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Rectangle;
import org.xmind.core.IHtmlNotesContent;
import org.xmind.core.IHyperlinkSpan;
import org.xmind.core.IImageSpan;
import org.xmind.core.INotesContent;
import org.xmind.core.IParagraph;
import org.xmind.core.IPlainNotesContent;
import org.xmind.core.ISpan;
import org.xmind.core.ITextSpan;
import org.xmind.core.internal.dom.NumberUtils;
import org.xmind.core.style.IStyle;
import org.xmind.core.style.IStyled;
import org.xmind.ui.resources.ColorUtils;
import org.xmind.ui.resources.FontUtils;
import org.xmind.ui.richtext.Hyperlink;
import org.xmind.ui.richtext.IRichDocument;
import org.xmind.ui.richtext.ImagePlaceHolder;
import org.xmind.ui.richtext.LineStyle;
import org.xmind.ui.richtext.RichDocument;
import org.xmind.ui.richtext.RichTextUtils;
import org.xmind.ui.style.StyleUtils;
import org.xmind.ui.style.Styles;
public class RichDocumentBuilder {
private static boolean DEBUG = false;
private RichDocumentNotesAdapter adapter;
private IRichDocument result;
private StringBuilder totalText = new StringBuilder();
private List<StyleRange> textStyles = new ArrayList<StyleRange>();
private List<LineStyle> lineStyles = new ArrayList<LineStyle>();
private List<ImagePlaceHolder> images = new ArrayList<ImagePlaceHolder>();
private List<Hyperlink> hyperlinks = new ArrayList<Hyperlink>();
private int lineIndex = 0;
private int totalOffset = 0;
private Stack<LineStyle> lineStyleStack = new Stack<LineStyle>();
private Stack<StyleRange> textStyleStack = new Stack<StyleRange>();
private StringBuilder currentLine = null;
private StyleRange lastTextStyle = null;
public RichDocumentBuilder(RichDocumentNotesAdapter adapter) {
this.adapter = adapter;
}
public void build(INotesContent content) {
if (content == null) {
if (DEBUG)
System.out.println("empty content"); //$NON-NLS-1$
result = new RichDocument();
return;
}
if (content instanceof IPlainNotesContent) {
String text = ((IPlainNotesContent) content).getTextContent();
if (text == null || "".equals(text)) { //$NON-NLS-1$
if (DEBUG)
System.out.println("empty plain content"); //$NON-NLS-1$
result = new RichDocument();
return;
}
if (DEBUG)
System.out.println("plain content"); //$NON-NLS-1$
result = new RichDocument(text);
return;
}
if (!(content instanceof IHtmlNotesContent)) {
if (DEBUG)
System.out.println("unknown content format"); //$NON-NLS-1$
result = new RichDocument();
return;
}
IHtmlNotesContent html = (IHtmlNotesContent) content;
// if (DEBUG)
// System.out.println(content);
// content = DOMUtils.makeElementText(content, NS.XMAP,
// WorkbookUtils.TAG_RICH_CONTENT, NS.Xhtml, NS.Xlink, NS.SVG);
// Document doc;
// try {
// doc = DOMUtils.loadDocument(content.getBytes());
// } catch (Throwable e) {
// if (DEBUG) {
// e.printStackTrace();
// } else {
// Logger.log(e, "Failed to parse html notes"); //$NON-NLS-1$
// }
// result = new RichDocument();
// return;
// }
readContent(html);
//
// readElement(doc.getDocumentElement());
if (endsWith(totalText, NotesConstants.LINE_DELIMITER)) {
if (DEBUG) {
System.out.println("-- delete last line delimiter -- "); //$NON-NLS-1$
}
deleteLastLineDelimiter();
}
result = new RichDocument(totalText.toString());
result.setTextStyles(
textStyles.toArray(new StyleRange[textStyles.size()]));
result.setLineStyles(
lineStyles.toArray(new LineStyle[lineStyles.size()]));
result.setImages(images.toArray(new ImagePlaceHolder[images.size()]));
result.setHyperlinks(
hyperlinks.toArray(new Hyperlink[hyperlinks.size()]));
if (DEBUG) {
System.out.println(result.get());
System.out.println(Arrays.toString(result.getTextStyles()));
System.out.println(Arrays.toString(result.getLineStyles()));
System.out.println(Arrays.toString(result.getImages()));
System.out.println(Arrays.toString(result.getHyperlinks()));
}
}
private boolean endsWith(StringBuilder sb, String s) {
if (s.length() == 0 || sb.length() < s.length())
return false;
for (int i = 0; i < s.length(); i++) {
char c = sb.charAt(sb.length() - i - 1);
char c2 = s.charAt(s.length() - i - 1);
if (c != c2)
return false;
}
return true;
}
private void deleteLastLineDelimiter() {
int length = NotesConstants.LENGTH_DELIMITER;
totalText.delete(totalText.length() - length, totalText.length());
if (lastTextStyle != null
&& lastTextStyle.start + lastTextStyle.length == totalOffset) {
if (lastTextStyle.length <= length) {
textStyles.remove(lastTextStyle);
} else {
lastTextStyle.length -= length;
}
}
}
private void readContent(IHtmlNotesContent html) {
for (IParagraph p : html.getParagraphs()) {
readParagraph(p);
}
}
private void readParagraph(IParagraph p) {
if (currentLine != null) {
endLine();
}
if (DEBUG) {
System.out.println("start line: " + lineIndex); //$NON-NLS-1$
}
LineStyle currentLineStyle = createLineStyle(p);
if (currentLineStyle != null) {
currentLineStyle.lineIndex = lineIndex;
lineStyles.add(currentLineStyle);
if (DEBUG)
System.out.println("line style added: " + currentLineStyle); //$NON-NLS-1$
}
lineStyleStack.push(currentLineStyle);
if (DEBUG)
System.out.println("line style pushed: " + currentLineStyle); //$NON-NLS-1$
currentLine = new StringBuilder();
readParagraphContent(p);
endLine();
lineStyleStack.pop();
}
private LineStyle createLineStyle(IParagraph p) {
LineStyle currentLineStyle = newLineStyle();
IStyle style = getStyle(p);
if (style != null) {
String alignment = style.getProperty(Styles.TextAlign);
if (DEBUG) {
System.out.println("alingment: " + alignment); //$NON-NLS-1$
}
if (alignment != null) {
currentLineStyle.alignment = toSWTAlignment(alignment);
}
// String bullet = style.getProperty(Styles.TextBullet);
// if (DEBUG)
// System.out.println("bullet: " + bullet); //$NON-NLS-1$
// if (bullet != null) {
// boolean isBullet = Styles.TEXT_STYLE_BULLET.equals(bullet) ? true
// : false;
// currentLineStyle.bullet = isBullet;
// }
String bulletStyle = style.getProperty(Styles.TextBullet);
if (DEBUG)
System.out.println("bulletStyle: " + bulletStyle); //$NON-NLS-1$
if (bulletStyle != null) {
currentLineStyle.bulletStyle = getBulletStyle(bulletStyle);
}
} else if (DEBUG) {
System.out.println("no line style"); //$NON-NLS-1$
}
return currentLineStyle;
}
private String getBulletStyle(String bulletStyle) {
if (LineStyle.BULLET.equals(bulletStyle))
return LineStyle.BULLET;
else if (LineStyle.NUMBER.equals(bulletStyle))
return LineStyle.NUMBER;
return LineStyle.NONE_STYLE;
}
private void readParagraphContent(IParagraph p) {
for (ISpan span : p.getSpans()) {
if (span instanceof IImageSpan) {
readImage((IImageSpan) span);
} else if (span instanceof ITextSpan) {
readText((ITextSpan) span);
} else if (span instanceof IHyperlinkSpan) {
readHyperlink((IHyperlinkSpan) span);
}
}
}
// private void readElementChildren(Element ele) {
// NodeList children = ele.getChildNodes();
// for (int i = 0; i < children.getLength(); i++) {
// Node child = children.item(i);
// short nodeType = child.getNodeType();
// if (nodeType == Node.ELEMENT_NODE) {
// readElement((Element) child);
// } else if (nodeType == Node.TEXT_NODE) {
// readText((Text) child);
// }
// }
// }
private void endLine() {
if (DEBUG) {
System.out.println("end line: " + lineIndex); //$NON-NLS-1$
}
sumLineIndent();
appendLineDelimiter();
lineIndex++;
currentLine = null;
}
private void ensureLineStart() {
if (currentLine != null)
return;
LineStyle currentLineStyle = newLineStyle();
currentLineStyle.lineIndex = lineIndex;
if (!lineStyleStack.isEmpty())
lineStyleStack.pop();
lineStyleStack.push(currentLineStyle);
currentLine = new StringBuilder();
}
private void appendLineDelimiter() {
totalText.append(NotesConstants.LINE_DELIMITER);
if (lastTextStyle != null
&& lastTextStyle.start + lastTextStyle.length == totalOffset) {
lastTextStyle.length += NotesConstants.LENGTH_DELIMITER;
}
totalOffset += NotesConstants.LENGTH_DELIMITER;
}
private void sumLineIndent() {
if (currentLine == null)
return;
if (lineStyleStack.isEmpty())
return;
int indent = calcIndentCount(currentLine);
LineStyle lastLineStyle = lineStyleStack.peek();
lastLineStyle.indent = indent;
}
private LineStyle newLineStyle() {
LineStyle lastLineStyle = lineStyleStack.isEmpty() ? null
: lineStyleStack.peek();
if (DEBUG) {
System.out.println("last line style: " + lastLineStyle); //$NON-NLS-1$
}
if (lastLineStyle == null)
return (LineStyle) RichTextUtils.DEFAULT_LINE_STYLE.clone();
return (LineStyle) lastLineStyle.clone();
}
private StyleRange newTextStyle() {
StyleRange lastTextStyle = textStyleStack.isEmpty() ? null
: textStyleStack.peek();
if (lastTextStyle == null)
return null;
return (StyleRange) lastTextStyle.clone();
}
private void readImage(IImageSpan span) {
String uri = span.getSource();//DOMUtils.getAttribute(ele, DOMConstants.ATTR_SRC);
if (uri != null) {
Image image = adapter.getImageFromUrl(uri);
if (image != null) {
ensureLineStart();
String s = ImagePlaceHolder.PLACE_HOLDER;
int length = s.length();
totalText.append(s);
currentLine.append(s);
StyleRange style = (StyleRange) RichTextUtils.DEFAULT_STYLE
.clone();
style.start = totalOffset;
style.length = length;
// style.data = image;
Rectangle rect = image.getBounds();
style.metrics = new GlyphMetrics(rect.height, 0, rect.width);
ImagePlaceHolder imagePlaceHolder = new ImagePlaceHolder(
totalOffset, image);
images.add(imagePlaceHolder);
if (!RichTextUtils.merge(style, lastTextStyle)) {
textStyles.add(style);
lastTextStyle = style;
}
totalOffset += length;
}
}
}
private void readHyperlink(IHyperlinkSpan span) {
String urlString = span.getHref();
int start = totalOffset;
for (ISpan s : span.getSpans()) {
if (s instanceof ITextSpan) {
readText((ITextSpan) s);
} else if (s instanceof IImageSpan) {
readImage((IImageSpan) s);
}
}
int length = totalOffset - start;
Hyperlink hyperlink = new Hyperlink(start, length, urlString);
hyperlinks.add(hyperlink);
}
private void readText(ITextSpan span) {
StyleRange textStyle = createTextStyle(getStyle(span));
textStyleStack.push(textStyle);
appendText(span.getTextContent(), textStyle);
//readElementChildren(ele);
textStyleStack.pop();
}
private void appendText(String text, StyleRange textStyle) {
ensureLineStart();
int length = text.length();
totalText.append(text);
currentLine.append(text);
if (textStyle != null) {
textStyle.start = totalOffset;
textStyle.length = length;
// Merge with last style range if possible
if (!RichTextUtils.merge(textStyle, lastTextStyle)) {
textStyles.add(textStyle);
lastTextStyle = textStyle;
}
}
totalOffset += length;
}
// private void readText(Text text) {
// appendText(text.getTextContent(), newTextStyle());
// }
private StyleRange createTextStyle(IStyle style) {
StyleRange textStyle = newTextStyle();
if (style == null)
return textStyle;
String name = style.getProperty(Styles.FontFamily);
String availableFontName = FontUtils.getAAvailableFontNameFor(name);
name = availableFontName != null ? availableFontName : name;
if (Styles.SYSTEM.equals(name)) {
name = JFaceResources.getDefaultFont().getFontData()[0].getName();
}
String height = style.getProperty(Styles.FontSize);
String weight = style.getProperty(Styles.FontWeight);
String fontStyle = style.getProperty(Styles.FontStyle);
String foreground = style.getProperty(Styles.TextColor);
String background = style.getProperty(Styles.BackgroundColor);
String decoration = style.getProperty(Styles.TextDecoration);
if (name == null && height == null && weight == null
&& fontStyle == null && foreground == null && background == null
&& decoration == null)
return textStyle;
if (name == null)
name = RichTextUtils.DEFAULT_FONT_DATA.getName();
int size = NumberUtils.safeParseInt(StyleUtils.trimNumber(height),
RichTextUtils.DEFAULT_FONT_DATA.getHeight());
boolean bold = weight != null
&& weight.contains(Styles.FONT_WEIGHT_BOLD);
boolean italic = fontStyle != null
&& fontStyle.contains(Styles.FONT_STYLE_ITALIC);
if (textStyle == null)
textStyle = new StyleRange();
textStyle.font = FontUtils.getFont(name, size, bold, italic);
textStyle.foreground = ColorUtils.getColor(foreground);
textStyle.background = ColorUtils.getColor(background);
textStyle.underline = decoration != null
&& decoration.contains(Styles.TEXT_DECORATION_UNDERLINE);
textStyle.strikeout = decoration != null
&& decoration.contains(Styles.TEXT_DECORATION_LINE_THROUGH);
return textStyle;
}
private int toSWTAlignment(String value) {
if (Styles.ALIGN_CENTER.equals(value))
return SWT.CENTER;
if (Styles.ALIGN_RIGHT.equals(value))
return SWT.RIGHT;
return SWT.LEFT;
}
private int calcIndentCount(StringBuilder line) {
int indent = 0;
for (int i = 0; i < line.length(); i++) {
char c = line.charAt(i);
if (c != '\t') {
return indent;
}
indent++;
}
return indent;
}
private IStyle getStyle(IStyled styled) {
String styleId = styled.getStyleId();
if (styleId != null) {
return adapter.getWorkbook().getStyleSheet().findStyle(styleId);
}
return null;
}
public IRichDocument getResult() {
return result;
}
}