/*******************************************************************************
* Copyright (c) 2009, Adobe Systems Incorporated
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* · Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* · Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* · Neither the name of Adobe Systems Incorporated nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*******************************************************************************/
package com.adobe.dp.office.conv;
import com.adobe.dp.css.*;
import com.adobe.dp.epub.io.BufferedDataSource;
import com.adobe.dp.epub.io.DataSource;
import com.adobe.dp.epub.io.StringDataSource;
import com.adobe.dp.epub.opf.OPSResource;
import com.adobe.dp.epub.opf.Publication;
import com.adobe.dp.epub.opf.Resource;
import com.adobe.dp.epub.opf.StyleResource;
import com.adobe.dp.epub.ops.Element;
import com.adobe.dp.epub.ops.HTMLElement;
import com.adobe.dp.epub.ops.ImageElement;
import com.adobe.dp.epub.ops.OPSDocument;
import com.adobe.dp.epub.style.Stylesheet;
import com.adobe.dp.office.metafile.WMFParser;
import com.adobe.dp.office.rtf.*;
import com.adobe.dp.otf.FontLocator;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
public class RTFConverter {
Stylesheet stylesheet;
RTFDocument doc;
Publication epub;
OPSDocument chapter;
Element section;
Element paragraph;
Element textAppendPoint;
HashSet usedClassNames = new HashSet();
Hashtable styleMap = new Hashtable();
String bulletText;
String bulletClass;
StyleResource css;
PrintWriter log = new PrintWriter(new OutputStreamWriter(System.out));
int count = 1;
static String mediaFolder = "OPS/images/";
class WMFResourceWriter implements ResourceWriter {
public StreamAndName createResource(String base, String suffix, boolean doNotCompress) throws IOException {
String name = mediaFolder + "wmf-" + base + suffix;
name = epub.makeUniqueResourceName(name);
BufferedDataSource dataSource = new BufferedDataSource();
epub.createBitmapImageResource(name, "image/png", dataSource);
return new StreamAndName(name.substring(mediaFolder.length()), dataSource.getOutputStream());
}
}
public RTFConverter(RTFDocument doc, Publication epub) {
this.doc = doc;
this.epub = epub;
}
private void transferProperties(SelectorRule rule, RTFStyle style, boolean block) {
Iterator props = style.properties();
boolean adjFontSize = false;
float defaultFontSize = 22;
float fontSize = defaultFontSize;
boolean underline = false;
boolean strike = false;
int before = 0;
int after = 0;
int left = 0;
int right = 0;
int lineSpacing = 0;
while (props.hasNext()) {
String prop = (String) props.next();
Object val = style.get(prop);
if (prop.equals("q_")) {
if (val.equals("l"))
rule.set("text-align", new CSSName("left"));
else if (val.equals("r"))
rule.set("text-align", new CSSName("right"));
else if (val.equals("c"))
rule.set("text-align", new CSSName("center"));
else if (val.equals("j"))
rule.set("text-align", new CSSName("justify"));
} else if (prop.equals("f")) {
RTFFont font = doc.getFont(((Number) val).intValue());
if (font != null)
rule.set("font-family", font.toCSSValue());
} else if (prop.equals("fs")) {
fontSize = ((Number) val).intValue();
} else if (prop.equals("sb")) {
before = ((Number) val).intValue();
} else if (prop.equals("sa")) {
after = ((Number) val).intValue();
} else if (prop.equals("li")) {
left = ((Number) val).intValue();
} else if (prop.equals("ri")) {
right = ((Number) val).intValue();
} else if (prop.equals("sl")) {
lineSpacing = ((Number) val).intValue();
} else if (prop.equals("fi")) {
rule.set("text-indent", new CSSLength(((Number) val).intValue() / 220.0, "em"));
} else if (prop.equals("i")) {
rule.set("font-style", new CSSName(val.equals(Boolean.TRUE) ? "italic" : "normal"));
} else if (prop.equals("b")) {
rule.set("font-weight", new CSSName(val.equals(Boolean.TRUE) ? "bold" : "normal"));
} else if (prop.equals("cf")) {
RTFColor color = doc.getColor(((Integer) val).intValue());
if (color != null)
rule.set("color", color.toCSSValue());
} else if (prop.equals("sub")) {
if (val.equals(Boolean.TRUE)) {
rule.set("vertical-align", new CSSName("sub"));
adjFontSize = true;
}
} else if (prop.equals("super")) {
if (val.equals(Boolean.TRUE)) {
rule.set("vertical-align", new CSSName("super"));
adjFontSize = true;
}
} else if (prop.equals("ul")) {
if (!val.equals(Boolean.FALSE)) {
underline = true;
}
} else if (prop.equals("strike")) {
if (!val.equals(Boolean.FALSE)) {
strike = true;
}
} else if (prop.equals("cf")) {
RTFColor color = doc.getColor(((Integer) val).intValue());
if (color != null)
rule.set("color", color.toCSSValue());
} else if (prop.equals("webhidden")) {
rule.set("visibility", new CSSName(val.equals(Boolean.TRUE) ? "hidden" : "visible"));
}
}
if (adjFontSize)
fontSize *= 0.67f;
if (fontSize != defaultFontSize)
rule.set("font-size", new CSSLength((fontSize / defaultFontSize), "em"));
if (underline || strike) {
if (underline && strike) {
CSSValue[] td = { new CSSName("line-through"), new CSSName("underline") };
rule.set("text-decoration", new CSSValueList(',', td));
} else if (underline)
rule.set("text-decoration", new CSSName("underline"));
else
rule.set("text-decoration", new CSSName("line-through"));
}
if (block) {
CSSValue[] margin = { new CSSLength(before / 20.0, "pt"), new CSSLength(right / 20.0, "pt"),
new CSSLength(after / 20.0, "pt"), new CSSLength(left / 20.0, "pt") };
rule.set("margin", new CSSValueList(' ', margin));
if (lineSpacing != 0) {
if (lineSpacing < 0)
lineSpacing = -lineSpacing;
rule.set("line-height", new CSSLength(lineSpacing / (defaultFontSize * 10), "em"));
}
}
}
private String getClassName(RTFStyle[] styles, String prefix, Set propSet) {
RTFStyle style = RTFStyle.collapse(styles, propSet);
if (style.isEmpty())
return prefix + "0";
String className = (String) styleMap.get(style);
if (className == null) {
className = prefix + (count++);
Selector selector = stylesheet.getSimpleSelector(null, className);
SelectorRule rule = stylesheet.getRuleForSelector(selector, true);
transferProperties(rule, style, prefix.equals("p"));
style.lock();
styleMap.put(style, className);
}
return className;
}
private String getParagraphClassName(RTFStyle paragraphStyle, RTFStyle characterStyle) {
RTFStyle[] styles = { characterStyle, paragraphStyle };
return getClassName(styles, "p", RTFControlType.paragraphProps);
}
private String getCharacterClassName(RTFStyle paragraphStyle, RTFStyle characterStyle) {
RTFStyle[] styles = { characterStyle, paragraphStyle };
return getClassName(styles, "c", RTFControlType.characterProps);
}
private void ensureParagraph(RTFStyle paragraphStyle, RTFStyle characterStyle) {
if (paragraph == null) {
paragraph = chapter.createElement("p");
paragraph.setClassName(getParagraphClassName(paragraphStyle, characterStyle));
section.add(paragraph);
textAppendPoint = null;
}
}
private Element getTextAppendPoint(RTFStyle paragraphStyle, RTFStyle characterStyle) {
ensureParagraph(paragraphStyle, characterStyle);
if (textAppendPoint == null) {
if (characterStyle != null && !characterStyle.isEmpty()) {
textAppendPoint = chapter.createElement("span");
textAppendPoint.setClassName(getCharacterClassName(paragraphStyle, characterStyle));
paragraph.add(textAppendPoint);
} else {
textAppendPoint = paragraph;
}
}
return textAppendPoint;
}
private void addChildren(RTFGroup g, RTFStyle paragraphStyle, RTFStyle characterStyle) {
RTFControl ctrl;
Object[] content = g.getContent();
for (int i = 0; i < content.length; i++) {
Object c = content[i];
if (c instanceof RTFControl) {
ctrl = (RTFControl) c;
RTFControlType ct = ctrl.getType();
String name = ctrl.getName();
if (name.equals("par")) {
// new paragraph
paragraph = null;
textAppendPoint = null;
} else if (name.equals("s")) {
int index = ctrl.getParam();
paragraphStyle = doc.getParagraphStyle(index);
textAppendPoint = null;
} else if (name.equals("cs")) {
int index = ctrl.getParam();
characterStyle = cloneStyle(doc.getCharacterStyle(index));
textAppendPoint = null;
} else {
if (ct instanceof RTFFormattingControlType) {
if (name.equals("pard"))
paragraphStyle = null;
if (characterStyle == null)
characterStyle = new RTFStyle();
if (characterStyle.isLocked()) {
characterStyle = characterStyle.cloneStyle();
}
ct.formattingExec(ctrl, characterStyle);
textAppendPoint = null;
}
}
} else if (c instanceof String) {
getTextAppendPoint(paragraphStyle, characterStyle);
if (bulletText != null) {
HTMLElement bulletSpan = chapter.createElement("span");
bulletSpan.setClassName(bulletClass);
textAppendPoint.add(bulletSpan);
bulletSpan.add(bulletText);
bulletText = null;
}
textAppendPoint.add(c);
} else if (c instanceof RTFGroup) {
RTFGroup sg = (RTFGroup) c;
ctrl = sg.getHead();
if (ctrl != null) {
String name = ctrl.getName();
if (name.equals("pict")) {
processPicture(sg);
continue;
}
if (name.equals("listtext")) {
extractBulletText(sg);
continue;
}
// System.out.println(name + " " + ctrl.isOptional());
if (ctrl.isOptional())
continue;
}
textAppendPoint = null;
addChildren(sg, cloneStyle(paragraphStyle), cloneStyle(characterStyle));
textAppendPoint = null;
}
}
}
private void extractBulletText(RTFGroup listtext) {
StringBuffer sb = new StringBuffer();
extractText(listtext, sb);
bulletText = sb.toString();
bulletClass = "listtext";
if (bulletText.equals("\u00B7\t")) {
bulletText = "\u2022\t";
bulletClass = "bullet";
} else if (bulletText.equals("o\t")) {
bulletText = "\u25E6\t";
bulletClass = "bullet";
}
}
private void extractText(RTFGroup listtext, StringBuffer sb) {
Object[] content = listtext.getContent();
for (int i = 0; i < content.length; i++) {
Object c = content[i];
if (c instanceof String)
sb.append(c);
else if (c instanceof RTFGroup) {
RTFGroup g = (RTFGroup) c;
RTFControl ctrl = g.getHead();
if (ctrl == null || !ctrl.isOptional())
extractText(g, sb);
}
}
}
private void processPicture(RTFGroup pict) {
paragraph = null;
textAppendPoint = null;
Object[] content = pict.getContent();
if (content.length == 0)
return;
String contentType = null;
int width = 0;
int scalex = 100;
for (int i = 0; i < content.length; i++) {
if (content[i] instanceof RTFControl) {
RTFControl c = (RTFControl) content[i];
String name = c.getName();
if (name.equals("emfblip"))
contentType = "image/x-emf";
else if (name.equals("pngblip"))
contentType = "image/png";
else if (name.equals("jpegblip"))
contentType = "image/jpeg";
else if (name.equals("wmetafile"))
contentType = "image/x-wmf";
else if (name.equals("picwgoal"))
width = c.getParam();
else if (name.equals("picscalex"))
scalex = c.getParam();
}
}
if (contentType == null)
return;
Object last = content[content.length - 1];
if (!(last instanceof byte[]))
return;
byte[] pictBytes = (byte[]) last;
Resource res;
if (contentType.equals("image/jpeg") || contentType.equals("image/png")) {
BufferedDataSource dataSource = new BufferedDataSource();
try {
dataSource.getOutputStream().write(pictBytes);
} catch (IOException e) {
throw new Error("Unexpected IOException for memory buffer stream: " + e.getMessage());
}
String resName = epub.makeUniqueResourceName(mediaFolder
+ (contentType.equals("image/jpeg") ? "pict.jpeg" : "pict.png"));
res = epub.createBitmapImageResource(resName, contentType, dataSource);
} else if (contentType.equals("image/x-wmf")) {
WMFResourceWriter dw = new WMFResourceWriter();
GDISVGSurface svg = new GDISVGSurface(dw);
try {
WMFParser parser = new WMFParser(new ByteArrayInputStream(pictBytes), svg);
parser.readAll();
} catch (IOException e) {
e.printStackTrace(log);
return;
}
DataSource dataSource = new StringDataSource(svg.getSVG());
String resName = epub.makeUniqueResourceName(mediaFolder + "pict.svg");
res = epub.createGenericResource(resName, "image/svg+xml", dataSource);
} else
return;
ImageElement imageElement = chapter.createImageElement("img");
imageElement.setImageResource(res);
InlineRule props = new InlineRule();
if (width > 0)
props.set("width", new CSSLength(width * scalex / 2000.0, "pt"));
props.set("max-width", new CSSLength(95, "%"));
imageElement.setStyle(props);
section.add(imageElement);
}
private static RTFStyle cloneStyle(RTFStyle s) {
if (s == null)
return null;
return s.cloneStyle();
}
public void convert() {
OPSResource ops = epub.createOPSResource("OPS/content.xhtml");
css = epub.createStyleResource("OPS/style.css");
stylesheet = css.getStylesheet();
SelectorRule bulletRule = stylesheet.getRuleForSelector(stylesheet.getSimpleSelector("span", "bullet"), true);
CSSValue[] names = { new CSSName("sans-serif") };
bulletRule.set("font-family", new CSSValueList(',', names));
epub.addToSpine(ops);
chapter = ops.getDocument();
chapter.addStyleResource(css);
section = chapter.getBody();
addChildren(doc.getRoot(), null, null);
}
void embedFonts(FontLocator fontLocator) {
epub.addFonts(css, fontLocator);
}
public void setLog(PrintWriter log) {
this.log = log;
}
}