package com.adobe.dp.office.conv;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Vector;
import com.adobe.dp.css.CSSImpliedValue;
import com.adobe.dp.css.CSSLength;
import com.adobe.dp.css.CSSName;
import com.adobe.dp.css.CSSQuotedString;
import com.adobe.dp.css.CSSValue;
import com.adobe.dp.css.CSSValueList;
import com.adobe.dp.css.InlineRule;
import com.adobe.dp.office.types.BorderSide;
import com.adobe.dp.office.types.FontFamily;
import com.adobe.dp.office.types.Paint;
import com.adobe.dp.office.types.RGBColor;
import com.adobe.dp.office.word.BaseProperties;
import com.adobe.dp.office.word.NumberingLabel;
import com.adobe.dp.office.word.ParagraphProperties;
import com.adobe.dp.office.word.RunProperties;
import com.adobe.dp.office.word.Style;
import com.adobe.dp.office.word.TableCellProperties;
import com.adobe.dp.office.word.TableProperties;
public class StyleConverter {
Style documentDefaultParagraphStyle;
double defaultFontSize = 22.0;
boolean usingPX;
private static final int RUN_COMPLEX = 1;
private static final int RUN_STYLE = 2;
private static final int RUN_BOLD = 4;
private static final int RUN_ITALIC = 8;
private static final int RUN_SUPER = 0x10;
private static final int RUN_SUB = 0x20;
StyleConverter(boolean usingPX) {
this.usingPX = usingPX;
}
void setDocumentDefaultParagraphStyle(Style documentDefaultParagraphStyle) {
this.documentDefaultParagraphStyle = documentDefaultParagraphStyle;
}
boolean usingPX() {
return usingPX;
}
void setDefaultFontSize(double defaultFontSize) {
this.defaultFontSize = defaultFontSize;
}
private String mapToElement(String styleId) {
if (styleId != null) {
if (styleId.equals("Title"))
return "h1";
if (styleId.equals("Heading1"))
return "h1";
if (styleId.equals("Heading2"))
return "h2";
if (styleId.equals("Heading3"))
return "h3";
if (styleId.equals("Heading4"))
return "h4";
if (styleId.equals("Heading5"))
return "h5";
if (styleId.equals("Heading6"))
return "h6";
}
return null;
}
private int getRunMask(RunProperties rp) {
int result = 0;
if (rp == null)
return 0;
if (rp.getRunStyle() != null)
result = RUN_STYLE;
if (!rp.isEmpty()) {
Iterator it = rp.properties();
while (it.hasNext()) {
String prop = (String) it.next();
if (prop.equals("b")) {
Object val = rp.get("b");
if (val.equals(Boolean.TRUE))
result |= RUN_BOLD;
} else if (prop.equals("i")) {
Object val = rp.get("i");
if (val.equals(Boolean.TRUE))
result |= RUN_ITALIC;
} else if (prop.equals("vertAlign")) {
Object val = rp.get("vertAlign");
if (val.equals("superscript"))
result |= RUN_SUPER;
else if (val.equals("subscript"))
result |= RUN_SUB;
} else {
result |= RUN_COMPLEX;
}
}
}
return result;
}
static void setIfNotPresent(InlineRule rule, String name, CSSValue value) {
Object val = rule.get(name);
if (val == null || val instanceof CSSImpliedValue)
rule.set(name, value);
}
CSSValueList convertBorderSide(BorderSide side) {
String type = side.getType();
if (type.equals("single"))
type = "solid";
else
type = "solid";
Paint paint = side.getColor();
CSSValue color = (paint instanceof RGBColor ? ((RGBColor) paint).toCSSValue() : new CSSName("black"));
double width = side.getWidth() / 8.0;
if (width < 1)
width = 1;
CSSValue[] list = { new CSSLength(width, "px"), new CSSName(type), color };
return new CSSValueList(' ', list);
}
private CSSValue getFontFamilyString(FontFamily family) {
String name = family.getName();
Vector list = new Vector();
if (name.indexOf(' ') >= 0) {
list.add(new CSSQuotedString(name));
} else {
list.add(new CSSName(name));
}
String backupName = null;
String shape = family.getFamily();
String pitch = family.getPitch();
if (pitch != null && pitch.equals("fixed")) {
backupName = "monospace";
} else if (shape != null) {
if (shape.equals("roman"))
backupName = "serif";
else if (shape.equals("swiss"))
backupName = "sans-serif";
}
if (backupName != null) {
list.add(new CSSName(backupName));
}
if (list.size() == 1)
return (CSSValue) list.get(0);
CSSValue[] arr = new CSSValue[list.size()];
list.copyInto(arr);
return new CSSValueList(',', arr);
}
public boolean convertLabelToProperty(NumberingLabel label, InlineRule rule) {
String text = label.getText();
if (text.length() == 1) {
int bulletChar = text.charAt(0);
if (bulletChar == 0xf0b7) {
// solid bullet
if (rule != null) {
rule.set("list-style-type", new CSSName("disc"));
rule.set("text-indent", new CSSLength(0, "px"));
}
return true;
} else if (bulletChar == 'o') {
if (rule != null) {
rule.set("list-style-type", new CSSName("circle"));
rule.set("text-indent", new CSSLength(0, "px"));
}
return true;
}
}
if (rule != null)
rule.set("list-style-type", new CSSName("none"));
return false;
}
public StylingResult getLabelRule(InlineRule paragraphRule, NumberingLabel label, float emScale) {
StylingResult result = new StylingResult();
if (label.getRunProperties() != null) {
convertStylingRule(result, label.getRunProperties(), emScale, false, false);
}
if (paragraphRule != null) {
Object textIndent = paragraphRule.get("text-indent");
if (textIndent instanceof CSSLength) {
CSSLength len = (CSSLength) textIndent;
if (len.getValue() < 0) {
CSSLength labelWidth = new CSSLength(-len.getValue(), len.getUnit());
result.elementRule.set("display", new CSSName("inline-block"));
result.elementRule.set("text-indent", new CSSLength(0, "px"));
result.elementRule.set("min-width", labelWidth);
}
}
}
result.elementClassName = "label";
return result;
}
private void setContainerIfNotPresent(StylingResult result, String prop, CSSValue value) {
if (result.containerRule == null)
result.containerRule = new InlineRule();
setIfNotPresent(result.containerRule, prop, value);
}
private void setElementIfNotPresent(StylingResult result, String prop, CSSValue value) {
setIfNotPresent(result.elementRule, prop, value);
}
private boolean keepTogether(Hashtable wprop) {
Object contextualSpacing = wprop.get("contextualSpacing");
if (contextualSpacing != null && contextualSpacing.equals(Boolean.TRUE))
return true;
return false;
}
private void convertWordToCSS(StylingResult result, Hashtable wprop, NumberingLabel label, float emScale,
boolean sameStyleBefore, boolean sameStyleAfter) {
final float normalWidth = 612; // convert to percentages of this
if (wprop.isEmpty())
return;
double fontSize = 1;
Object value = wprop.get("sz");
if (value != null)
fontSize = ((Number) value).doubleValue() / (emScale * defaultFontSize);
value = wprop.get("vertAlign");
if (value != null) {
if (value.equals("superscript")) {
setElementIfNotPresent(result, "vertical-align", new CSSName("super"));
fontSize *= 0.8;
} else if (value.equals("subscript")) {
setElementIfNotPresent(result, "vertical-align", new CSSName("sub"));
fontSize *= 0.8;
}
}
if (fontSize != 1) {
if (usingPX)
setElementIfNotPresent(result, "font-size", new CSSLength(fontSize * emScale * defaultFontSize / 2,
"px"));
else
setElementIfNotPresent(result, "font-size", new CSSLength(fontSize, "em"));
}
double containerFontSize = defaultFontSize * emScale;
double elementFontSize = containerFontSize * fontSize;
Number indLeft = (Number) wprop.get("ind-left");
Number indHanging = (Number) wprop.get("ind-hanging");
Number indFirstLine = (Number) wprop.get("ind-firstLine");
boolean isList = label != null || result.elementName != null && result.elementName.equals("li");
if (indLeft != null || indHanging != null || indFirstLine != null) {
double left = indLeft == null ? 0 : indLeft.doubleValue();
double hang = indHanging == null ? 0 : indHanging.doubleValue();
double indent = indFirstLine == null ? 0 : indFirstLine.doubleValue();
indent -= hang;
if (isList) {
double halfPtSize = left / 10.0;
if (usingPX || indent < 0 || isList) {
double pxSize = halfPtSize / 2;
setElementIfNotPresent(result, "margin-left", new CSSLength(pxSize, "px"));
} else {
double remSize = halfPtSize / elementFontSize;
setElementIfNotPresent(result, "margin-left", new CSSLength(remSize, "em"));
}
} else if (left > 0) {
double pts = left / 20;
if (pts > 0) {
if (isList) {
setElementIfNotPresent(result, "margin-left", new CSSLength(pts, "px"));
} else {
double percent = 100 * pts / normalWidth;
setContainerIfNotPresent(result, "margin-left", new CSSLength(percent, "%"));
}
}
}
if (indent != 0) {
double halfPtSize = indent / 10.0;
if (usingPX || indent < 0 || isList) {
double pxSize = halfPtSize / 2;
setElementIfNotPresent(result, "text-indent", new CSSLength(pxSize, "px"));
} else {
double remSize = halfPtSize / elementFontSize;
setElementIfNotPresent(result, "text-indent", new CSSLength(remSize, "em"));
}
}
}
Iterator props = wprop.keySet().iterator();
while (props.hasNext()) {
String name = (String) props.next();
value = wprop.get(name);
if (name.equals("b")) {
boolean bold = value.equals(Boolean.TRUE);
setElementIfNotPresent(result, "font-weight", new CSSName(bold ? "bold" : "normal"));
} else if (name.equals("i")) {
boolean italic = value.equals(Boolean.TRUE);
setElementIfNotPresent(result, "font-style", new CSSName(italic ? "italic" : "normal"));
} else if (name.equals("rFonts")) {
FontFamily fontFamily = (FontFamily) value;
setElementIfNotPresent(result, "font-family", getFontFamilyString(fontFamily));
} else if (name.equals("u")) {
Object val = result.elementRule.get("text-decoration");
if (val == null || !val.equals("line-through"))
result.elementRule.set("text-decoration", new CSSName("underline"));
else {
CSSValue[] td = { new CSSName("line-through"), new CSSName("underline") };
result.elementRule.set("text-decoration", new CSSValueList(',', td));
}
} else if (name.equals("strike")) {
Object val = result.elementRule.get("text-decoration");
if (val == null || !val.equals("underline"))
result.elementRule.set("text-decoration", new CSSName("line-through"));
else {
CSSValue[] td = { new CSSName("line-through"), new CSSName("underline") };
result.elementRule.set("text-decoration", new CSSValueList(',', td));
}
} else if (name.equals("color")) {
setElementIfNotPresent(result, "color", ((RGBColor) value).toCSSValue());
} else if (name.equals("highlight")) {
setElementIfNotPresent(result, "background-color", ((RGBColor) value).toCSSValue());
} else if (name.equals("shd")) {
if (value instanceof RGBColor)
setContainerIfNotPresent(result, "background-color", ((RGBColor) value).toCSSValue());
} else if (name.startsWith("gridSpan")) {
result.cols = (Integer) value;
} else if (name.startsWith("vAlign")) {
String val = value.toString();
if (val.equals("center"))
result.elementRule.set("vertical-align", new CSSName("middle"));
else if (val.equals("top"))
result.elementRule.set("vertical-align", new CSSName("top"));
else if (val.equals("bottom"))
result.elementRule.set("vertical-align", new CSSName("bottom"));
} else if (name.startsWith("border-")) {
BorderSide side = (BorderSide) value;
CSSLength paddingDef = new CSSLength(side.getSpace() / 8.0, "px");
CSSValue borderDef = convertBorderSide(side);
if (name.equals("border-insideH")) {
if (result.tableCellRule == null)
result.tableCellRule = new InlineRule();
result.tableCellRule.set("padding-top", paddingDef);
result.tableCellRule.set("padding-bottom", paddingDef);
result.tableCellRule.set("border-top", borderDef);
result.tableCellRule.set("border-bottom", borderDef);
} else if (name.equals("border-insideV")) {
if (result.tableCellRule == null)
result.tableCellRule = new InlineRule();
result.tableCellRule.set("padding-left", paddingDef);
result.tableCellRule.set("padding-right", paddingDef);
result.tableCellRule.set("border-left", borderDef);
result.tableCellRule.set("border-right", borderDef);
} else if (name.equals("border-top")) {
setContainerIfNotPresent(result, "padding-top", paddingDef);
setContainerIfNotPresent(result, "border-top", borderDef);
} else if (name.equals("border-bottom")) {
setContainerIfNotPresent(result, "padding-bottom", paddingDef);
setContainerIfNotPresent(result, "border-bottom", borderDef);
} else if (name.equals("border-left")) {
setContainerIfNotPresent(result, "padding-left", paddingDef);
setContainerIfNotPresent(result, "border-left", borderDef);
} else if (name.equals("border-right")) {
setContainerIfNotPresent(result, "padding-right", paddingDef);
setContainerIfNotPresent(result, "border-right", borderDef);
}
} else if (name.equals("jc")) {
String css = "left";
if (value.equals("both"))
css = "justify";
else if (value.equals("right") || value.equals("center"))
css = value.toString();
setElementIfNotPresent(result, "text-align", new CSSName(css));
} else if (name.equals("webHidden")) {
boolean hidden = value.equals(Boolean.TRUE);
if (hidden)
setElementIfNotPresent(result, "display", new CSSName("none"));
} else if (name.equals("pageBreakBefore")) {
boolean pageBreakBefore = value.equals(Boolean.TRUE);
if (pageBreakBefore)
setContainerIfNotPresent(result, "page-break-before", new CSSName("always"));
} else if (name.equals("keepNext")) {
if (value.equals(Boolean.TRUE))
setContainerIfNotPresent(result, "page-break-after", new CSSName("avoid"));
} else if (name.equals("keepLines")) {
if (value.equals(Boolean.TRUE))
setContainerIfNotPresent(result, "page-break-inside", new CSSName("avoid"));
} else if (name.equals("spacing-before")) {
if (sameStyleBefore && keepTogether(wprop)) {
// vertical margin is zeroed out in this case
} else {
double halfPtSize = ((Number) value).doubleValue() / 10.0;
CSSLength len;
boolean marginOnElement = sameStyleBefore;
if (usingPX) {
double pxSize = halfPtSize / 2;
len = new CSSLength(pxSize, "px");
} else {
double remSize = halfPtSize / containerFontSize;
len = new CSSLength(remSize, "em");
}
if (marginOnElement) {
setElementIfNotPresent(result, "margin-top", len);
} else {
setContainerIfNotPresent(result, "margin-top", len);
}
}
} else if (name.equals("spacing-after")) {
if (sameStyleAfter && keepTogether(wprop)) {
// vertical margin is zeroed out in this case
} else {
double halfPtSize = ((Number) value).doubleValue() / 10.0;
CSSLength len;
boolean marginOnElement = sameStyleAfter;
if (usingPX) {
double pxSize = halfPtSize / 2;
len = new CSSLength(pxSize, "px");
} else {
double remSize = halfPtSize / containerFontSize;
len = new CSSLength(remSize, "em");
}
if (marginOnElement) {
setElementIfNotPresent(result, "margin-bottom", len);
} else {
setContainerIfNotPresent(result, "margin-bottom", len);
}
}
} else if (name.equals("spacing-line")) {
String lineRule = (String) wprop.get("spacing-lineRule");
double halfPtSize = ((Number) value).doubleValue() / 10.0;
if (halfPtSize > 0) {
if (usingPX && lineRule != null) {
double pxSize = halfPtSize / 2;
setElementIfNotPresent(result, "line-height", new CSSLength(pxSize, "px"));
} else {
double base = elementFontSize;
if (lineRule != null && lineRule.equals("auto"))
base = 24.0;
double remSize = halfPtSize / base;
setElementIfNotPresent(result, "line-height", new CSSLength(remSize, ""));
}
}
} else if (name.equals("ind-right")) {
double pts = ((Number) value).doubleValue() / 20.0;
if (pts > 0) {
if (isList) {
setElementIfNotPresent(result, "margin-right", new CSSLength(pts, "px"));
} else {
double percent = 100 * pts / normalWidth;
setContainerIfNotPresent(result, "margin-right", new CSSLength(percent, "%"));
}
}
} else if (name.equals("framePr-align")) {
String align = (String) value;
if (align != null) {
setContainerIfNotPresent(result, "float", new CSSName(align));
} else {
setContainerIfNotPresent(result, "float", new CSSImpliedValue(new CSSName("left")));
}
} else if (name.equals("framePr-w")) {
float width = ((Number) value).floatValue();
if (width > 0) {
float pts = width / 20;
float percent = 100 * pts / normalWidth;
setContainerIfNotPresent(result, "width", new CSSLength(percent, "%"));
// extra space indicates it was not explicitly set
setContainerIfNotPresent(result, "float", new CSSImpliedValue(new CSSName("left")));
}
} else if (name.equals("framePr-hSpace")) {
float hSpace = ((Number) value).floatValue();
if (hSpace > 0) {
String align = (String) wprop.get("framePr-align");
float pts = hSpace / 20;
CSSLength margin = new CSSLength(pts, "px");
if (align == null || align.equals("left")) {
setContainerIfNotPresent(result, "margin-right", margin);
setContainerIfNotPresent(result, "float", new CSSImpliedValue(new CSSName("left")));
} else
setContainerIfNotPresent(result, "margin-left", margin);
}
} else if (name.equals("framePr-vSpace")) {
float vSpace = ((Number) value).floatValue();
if (vSpace > 0) {
float pts = vSpace / 20;
setContainerIfNotPresent(result, "margin-bottom", new CSSLength(pts, "px"));
setContainerIfNotPresent(result, "float", new CSSImpliedValue(new CSSName("left")));
}
}
}
if (label != null)
convertLabelToProperty(label, result.elementRule);
}
private void cascade(Hashtable target, BaseProperties src, boolean force) {
if (src == null || src.isEmpty())
return;
Iterator props = src.properties();
while (props.hasNext()) {
String prop = (String) props.next();
if (force || target.get(prop) == null)
target.put(prop, src.get(prop));
}
}
Hashtable cascadeWordProperties(BaseProperties prop) {
boolean runOnly = prop instanceof RunProperties;
boolean tableOnly = prop instanceof TableProperties;
Style style;
NumberingLabel label = null;
Hashtable wprop = new Hashtable();
if (prop instanceof RunProperties) {
RunProperties rp = (RunProperties) prop;
cascade(wprop, rp, false);
style = rp.getRunStyle();
} else if (prop instanceof ParagraphProperties) {
ParagraphProperties pp = (ParagraphProperties) prop;
label = pp.getNumberingLabel();
cascade(wprop, pp, false);
style = pp.getParagraphStyle();
} else if (prop instanceof TableCellProperties) {
TableCellProperties tcp = (TableCellProperties) prop;
cascade(wprop, tcp, false);
style = null;
} else if (prop instanceof TableProperties) {
TableProperties tp = (TableProperties) prop;
cascade(wprop, tp, false);
style = ((TableProperties) prop).getTableStyle();
} else {
System.err.println("Unimplemented cascade: " + prop.getClass());
style = null;
}
while (style != null) {
if (tableOnly)
cascade(wprop, style.getTableProperties(), false);
else {
cascade(wprop, style.getRunProperties(), false);
if (!runOnly)
cascade(wprop, style.getParagraphProperties(), false);
}
style = style.getParent();
}
if (label != null) {
ParagraphProperties pp = label.getParagraphProperties();
cascade(wprop, pp, true);
}
if (!runOnly && documentDefaultParagraphStyle != null)
cascade(wprop, documentDefaultParagraphStyle.getParagraphProperties(), false);
return wprop;
}
private void convertStylingRule(StylingResult result, BaseProperties prop, float emScale, boolean sameStyleBefore,
boolean sameStyleAfter) {
Hashtable wprop = cascadeWordProperties(prop);
NumberingLabel label = null;
if (prop instanceof ParagraphProperties)
label = ((ParagraphProperties) prop).getNumberingLabel();
convertWordToCSS(result, wprop, label, emScale, sameStyleBefore, sameStyleAfter);
if (result.elementName != null && result.elementName.startsWith("h")) {
if (result.elementRule.get("font-weight") == null)
result.elementRule.set("font-weight", new CSSName("normal"));
if (result.elementRule.get("margin-top") == null)
result.elementRule.set("margin-top", new CSSLength(0, "px"));
if (result.elementRule.get("margin-bottom") == null)
result.elementRule.set("margin-bottom", new CSSLength(0, "px"));
if (result.elementRule.get("margin-left") == null)
result.elementRule.set("margin-left", new CSSLength(0, "px"));
if (result.elementRule.get("margin-right") == null)
result.elementRule.set("margin-right", new CSSLength(0, "px"));
}
}
public StylingResult convertTableStylingRule(TableProperties prop, float emScale) {
Hashtable wprop = cascadeWordProperties(prop);
StylingResult result = new StylingResult();
result.containerRule = result.elementRule;
convertWordToCSS(result, wprop, null, emScale, false, false);
return result;
}
public StylingResult convertTableCellStylingRule(TableCellProperties prop, float emScale,
InlineRule tableGlobalProps) {
Hashtable wprop = cascadeWordProperties(prop);
StylingResult result = new StylingResult();
result.containerRule = result.elementRule;
convertWordToCSS(result, wprop, null, emScale, false, false);
if (tableGlobalProps != null && !tableGlobalProps.isEmpty()) {
Iterator tgpi = tableGlobalProps.properties();
while (tgpi.hasNext()) {
String tgpn = (String) tgpi.next();
CSSValue tgpv = tableGlobalProps.get(tgpn);
setIfNotPresent(result.elementRule, tgpn, tgpv);
}
}
return result;
}
StylingResult styleElement(BaseProperties prop, boolean isListElement, float emScale, boolean sameStyleBefore,
boolean sameStyleAfter) {
StylingResult result = new StylingResult();
if (prop == null)
return result;
if (prop instanceof ParagraphProperties) {
ParagraphProperties pp = (ParagraphProperties) prop;
Style style = pp.getParagraphStyle();
boolean noInlineStyling = pp.isEmpty() && pp.getNumberingLabel() == null && pp.getRunProperties() == null;
if (style == null) {
if (noInlineStyling)
return result;
} else if (isListElement) {
result.elementName = "li";
} else {
result.elementName = mapToElement(style.getStyleId());
}
if (result.elementName == null && style != null) {
String styleId = style.getStyleId();
if (styleId.equals("Normal"))
result.elementClassName = "p";
else
result.elementClassName = styleId;
} else {
if (!noInlineStyling || result.elementName.equals("h1") || result.elementName.equals("li")
|| style == null)
result.elementClassName = "p";
else
result.elementClassName = style.getStyleId();
}
} else {
RunProperties rp = (RunProperties) prop;
int runMask = getRunMask(rp);
switch (runMask) {
case 0:
// don't create unneeded span element
return result;
case RUN_BOLD:
// bold only
result.elementName = "b";
return result;
case RUN_ITALIC:
// italic only
result.elementName = "i";
return result;
case RUN_SUB:
// subscript only
result.elementName = "sub";
return result;
case RUN_SUPER:
// superscript only
result.elementName = "sup";
return result;
}
if ((runMask & RUN_STYLE) != 0) {
String styleId = rp.getRunStyle().getStyleId();
if (styleId.equals("DefaultParagraphFont"))
result.elementClassName = "r"; // shorten it
else
result.elementClassName = styleId;
} else {
result.elementClassName = "r";
}
}
convertStylingRule(result, prop, emScale, sameStyleBefore, sameStyleAfter);
return result;
}
}