/** * Copyright (C) 2011-2015 The XDocReport Team <xdocreport@googlegroups.com> * * All rights reserved. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ package fr.opensagres.xdocreport.document.odt.textstyling; import java.text.DecimalFormat; import java.text.DecimalFormatSymbols; import java.util.regex.Matcher; import java.util.regex.Pattern; import fr.opensagres.xdocreport.document.textstyling.properties.ContainerProperties; import fr.opensagres.xdocreport.document.textstyling.properties.ContainerProperties.ContainerType; import fr.opensagres.xdocreport.document.textstyling.properties.TextAlignment; /** * Default implementation : - uses OOo default styles for headers - uses default but renamed styles for others * * @author <a href="mailto:tdelprat@nuxeo.com">Tiry</a> */ public class ODTDefaultStylesGenerator implements IODTStylesGenerator { protected static final String HEADER_PREFIX = "Heading_20_"; protected static final Pattern HEADER_PATTERN = Pattern.compile(HEADER_PREFIX + "(\\d+).*"); protected static final String[] BULLET_CHARS = { "\u2022", "\u25e6", "\u25aa" }; protected static final String OL_STYLE_NAME = "XDocReport_OL"; protected static final String UL_STYLE_NAME = "XDocReport_UL"; protected static final String LIST_P_STYLE_NAME_SUFFIX = "_P"; protected static final double BULLET_STEP = 0.635; protected static final String BOLD_STYLE_NAME = "XDocReport_Bold"; protected static final String ITALIC_STYLE_NAME = "XDocReport_Italic"; protected static final String UNDERLINE_STYLE_NAME = "XDocReport_Underline"; protected static final String STRIKE_STYLE_NAME = "XDocReport_Strike"; protected static final String SUBSCRIPT_STYLE_NAME = "XDocReport_Subscript"; protected static final String SUPERSCRIPT_STYLE_NAME = "XDocReport_Superscript"; protected static final String PAGE_BREAK_BEFORE_PARAGRAPH_STYLE_NAME = "XDocReport_ParaBreakBefore"; protected static final String PAGE_BREAK_AFTER_PARAGRAPH_STYLE_NAME = "XDocReport_ParaBreakAfter"; protected static final String EMPTY_TEXT_STYLE_NAME = "XDocReport_EmptyText"; // font size for the default OOo Headers protected static String[] TITLE_FONT_SIZE = { "115%", "14pt", "14pt", "85%", "85%", "75%" }; private long dynamicParagraphStylesNb = 0; private long dynamicTextStylesNb = 0; private final StringBuilder dynamicStyles; private String styleName = null; private ODTStyleProperties propertiesKind; private boolean hasPropertiesKind; private enum ODTStyleProperties { TEXT, PARAGRAPH } /** * protected static String[] HEADING_STYLES = new String[] { * "<style:style style:name=\"Heading_20_1\" style:display-name=\"Heading 1\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"1\" style:class=\"text\"><style:text-properties " * + * "fo:font-size=\"115%\" fo:font-weight=\"bold\" style:font-size-asian=\"115%\" style:font-weight-asian=\"bold\" style:font-size-complex=\"115%\" style:font-weight-complex=\"bold\"/></style:style>" * , * "<style:style style:name=\"Heading_20_2\" style:display-name=\"Heading 2\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"2\" style:class=\"text\"><style:text-properties fo:font-size=\"14pt\" fo:font-style=\"italic\" fo:font-weight=\"bold\" style:font-size-asian=\"14pt\" style:font-style-asian=\"italic\" style:font-weight-asian=\"bold\" style:font-size-complex=\"14pt\" style:font-style-complex=\"italic\" style:font-weight-complex=\"bold\"/></style:style>" * , * "<style:style style:name=\"Heading_20_3\" style:display-name=\"Heading 3\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"3\" style:class=\"text\"><style:text-properties fo:font-size=\"14pt\" fo:font-weight=\"bold\" style:font-size-asian=\"14pt\" style:font-weight-asian=\"bold\" style:font-size-complex=\"14pt\" style:font-weight-complex=\"bold\"/></style:style>" * , * "<style:style style:name=\"Heading_20_4\" style:display-name=\"Heading 4\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"4\" style:class=\"text\"><style:text-properties fo:font-size=\"85%\" fo:font-style=\"italic\" fo:font-weight=\"bold\" style:font-size-asian=\"85%\" style:font-style-asian=\"italic\" style:font-weight-asian=\"bold\" style:font-size-complex=\"85%\" style:font-style-complex=\"italic\" style:font-weight-complex=\"bold\"/></style:style>" * , * "<style:style style:name=\"Heading_20_5\" style:display-name=\"Heading 5\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"5\" style:class=\"text\"><style:text-properties fo:font-size=\"85%\" fo:font-weight=\"bold\" style:font-size-asian=\"85%\" style:font-weight-asian=\"bold\" style:font-size-complex=\"85%\" style:font-weight-complex=\"bold\"/></style:style>" * , * "<style:style style:name=\"Heading_20_6\" style:display-name=\"Heading 6\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" style:default-outline-level=\"6\" style:class=\"text\"><style:text-properties fo:font-size=\"75%\" fo:font-weight=\"bold\" style:font-size-asian=\"75%\" style:font-weight-asian=\"bold\" style:font-size-complex=\"75%\" style:font-weight-complex=\"bold\"/></style:style>" * }; */ public ODTDefaultStylesGenerator() { this.dynamicParagraphStylesNb = 0; this.dynamicTextStylesNb = 0; this.dynamicStyles = new StringBuilder(); } protected String getBulletChar( int level ) { return BULLET_CHARS[( level - 1 ) % 3]; } public String getHeaderStyleName( int level ) { return HEADER_PREFIX + level; } public String generateHeaderStyle( int level ) { StringBuilder style = new StringBuilder( "<style:style style:name=\"" ); style.append( getHeaderStyleName( level ) ); style.append( "\" style:display-name=\"" ); style.append( "Heading " + level ); style.append( "\" style:family=\"paragraph\" style:parent-style-name=\"Heading\" style:next-style-name=\"Text_20_body\" " ); style.append( "style:default-outline-level=\"" ); style.append( level + "\" " ); style.append( "style:class=\"text\"><style:text-properties " ); style.append( "fo:font-size=\"" ); style.append( TITLE_FONT_SIZE[level - 1] ); style.append( "\" fo:font-weight=\"bold\" " ); if ( level % 2 == 0 ) { style.append( " fo:font-style=\"italic\" " ); } style.append( "/></style:style>" ); return style.toString(); } public String generateTextStyles() { StringBuilder region = new StringBuilder(); region.append( generateStyle( EMPTY_TEXT_STYLE_NAME, "" ) ); region.append( generateStyle( BOLD_STYLE_NAME, "fo:font-weight=\"bold\"" ) ); region.append( generateStyle( ITALIC_STYLE_NAME, "fo:font-style=\"italic\"" ) ); region.append( generateStyle( UNDERLINE_STYLE_NAME, "style:text-underline-style=\"solid\" style:text-underline-width=\"auto\" style:text-underline-color=\"font-color\"" ) ); region.append( generateStyle( STRIKE_STYLE_NAME, "style:text-line-through-style=\"solid\" style:text-underline-style=\"none\"" ) ); region.append( generateStyle( SUBSCRIPT_STYLE_NAME, "style:text-position=\"sub\"" ) ); region.append( generateStyle( SUPERSCRIPT_STYLE_NAME, "style:text-position=\"super\"" ) ); return region.toString(); } public String generateParagraphStyles() { StringBuilder region = new StringBuilder(); region.append( generateStylePageBreak( true ) ); region.append( generateStylePageBreak( false ) ); return region.toString(); } private String generateStyle( String styleName, String style ) { StringBuilder region = new StringBuilder(); region.append( "<style:style style:name=\"" ); region.append( styleName ); region.append( "\" style:family=\"text\">" ); region.append( "<style:text-properties " ); region.append( style ); region.append( "/></style:style>" ); return region.toString(); } public String generateListStyle() { StringBuilder region = new StringBuilder(); region.append( generateListStyle( true ) ); region.append( generateListStyle( false ) ); return region.toString(); } protected String generateListStyle( boolean ordered ) { StringBuilder region = new StringBuilder(); String styleName = UL_STYLE_NAME; if ( ordered ) { styleName = OL_STYLE_NAME; } // generate Paragraph styles reference region.append( "<style:style style:name=\"" ); region.append( styleName + LIST_P_STYLE_NAME_SUFFIX ); region.append( "\" style:family=\"paragraph\" style:parent-style-name=\"Standard\" style:list-style-name=\"" ); region.append( styleName ); region.append( "\"/>" ); // generate the list style region.append( "<text:list-style style:name=\"" ); region.append( styleName ); region.append( "\">" ); // generate styles for 10 levels for ( int level = 1; level <= 10; level++ ) { region.append( generateBulletStyle( level, ordered ) ); } region.append( "</text:list-style>" ); return region.toString(); } protected String generateBulletStyle( Integer level, boolean ordered ) { StringBuilder region = new StringBuilder(); if ( ordered ) { region.append( "<text:list-level-style-number text:level=\"" ); } else { region.append( "<text:list-level-style-bullet text:level=\"" ); } region.append( level.toString() ); region.append( "\" text:style-name=\"" ); if ( ordered ) { region.append( "Numbering_20_Symbols" ); } else { region.append( "Bullet_20_Symbols" ); } region.append( "\" style:num-suffix=\".\" " ); if ( ordered ) { region.append( "style:num-format=\"1\">" ); } else { region.append( "text:bullet-char=\"" + getBulletChar( level ) + "\">" ); } region.append( "<style:list-level-properties text:list-level-position-and-space-mode=\"label-alignment\">" ); region.append( "<style:list-level-label-alignment text:label-followed-by=\"listtab\" " ); DecimalFormatSymbols decimalFormat = new DecimalFormatSymbols(); decimalFormat.setDecimalSeparator( '.' ); String offset = new DecimalFormat( "#.###", decimalFormat ).format( BULLET_STEP * ( level + 1 ) ); region.append( " text:list-tab-stop-position=\"" ); region.append( offset ); region.append( "cm\" fo:text-indent=\"-0.635cm\" fo:margin-left=\"" ); region.append( offset ); region.append( "cm\"/>" ); region.append( "</style:list-level-properties>" ); if ( ordered ) { region.append( "</text:list-level-style-number>" ); } else { region.append( "</text:list-level-style-bullet>" ); } return region.toString(); } public int getHeaderStylesCount() { return 6; } public int getHeaderStyleNameLevel( String styleName ) { if ( styleName == null ) { return -1; } Matcher matcher = HEADER_PATTERN.matcher(styleName); if ( !matcher.find() ) { return -1; } String headerIdx = matcher.group(1); return Integer.parseInt( headerIdx ); } public String getOLStyleName() { return OL_STYLE_NAME; } public String getULStyleName() { return UL_STYLE_NAME; } public String getListItemParagraphStyleNameSuffix() { return LIST_P_STYLE_NAME_SUFFIX; } public String getBoldStyleName() { return BOLD_STYLE_NAME; } public String getItalicStyleName() { return ITALIC_STYLE_NAME; } //@Override public String getUnderlineStyleName() { return UNDERLINE_STYLE_NAME; } //@Override public String getStrikeStyleName() { return STRIKE_STYLE_NAME; } //@Override public String getSubscriptStyleName() { return SUBSCRIPT_STYLE_NAME; } //@Override public String getSuperscriptStyleName() { return SUPERSCRIPT_STYLE_NAME; } public String getParaBreakBeforeStyleName() { return PAGE_BREAK_BEFORE_PARAGRAPH_STYLE_NAME; } public String getParaBreakAfterStyleName() { return PAGE_BREAK_AFTER_PARAGRAPH_STYLE_NAME; } private String generateStylePageBreak( boolean pageBreakBefore ) { String styleName = pageBreakBefore ? PAGE_BREAK_BEFORE_PARAGRAPH_STYLE_NAME : PAGE_BREAK_AFTER_PARAGRAPH_STYLE_NAME; StringBuilder style = new StringBuilder(); style.append( "<style:style style:name=\"" ); style.append( styleName ); style.append( "\" style:family=\"paragraph\" >" ); style.append( "<style:paragraph-properties" ); if ( pageBreakBefore ) { style.append( " fo:break-before=\"page\"" ); } else { style.append( " fo:break-after=\"page\"" ); } style.append( " />" ); style.append( "</style:style>" ); return style.toString(); } //@Override public String generateAllStyles( ODTDefaultStyle defaultStyle ) { StringBuilder styles = new StringBuilder(); // Add bold, italic, bold+italic styles for text styling. styles.append( generateTextStyles() ); // Add paragraph styles for text styling. styles.append( generateParagraphStyles() ); // Add styles for lists styles.append( generateListStyle() ); styles.append( getDynamicStyles() ); return styles.toString(); } public String getDynamicStyles() { return dynamicStyles.toString(); } //@Override public String getTextStyleName( ContainerProperties properties ) { if ( properties == null ) { return EMPTY_TEXT_STYLE_NAME; } this.styleName = null; // <style:text-properties setPropertiesKind( ODTStyleProperties.TEXT ); if ( properties.isBold() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:font-weight=\"bold\" " ); } if ( properties.isItalic() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:font-style=\"italic\" " ); } if ( properties.isUnderline() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "style:text-underline-style=\"solid\" style:text-underline-width=\"auto\" style:text-underline-color=\"font-color\" " ); if ( properties.isStrike() ) { dynamicStyles.append( "style:text-line-through-style=\"solid\" " ); } } else { if ( properties.isStrike() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "style:text-underline-style=\"none\" style:text-line-through-style=\"solid\" " ); } } if ( properties.isSubscript() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "style:text-position=\"sub\" " ); } if ( properties.isSuperscript() ) { startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "style:text-position=\"super\" " ); } // <style:paragraph-properties fo:text-align="center" style:justify-single-word="false" /> setPropertiesKind( ODTStyleProperties.PARAGRAPH ); TextAlignment textAlignment = properties.getTextAlignment(); if ( textAlignment != null ) { switch ( textAlignment ) { case Center: startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:text-align=\"center\" style:justify-single-word=\"false\" " ); break; case Justify: startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:text-align=\"justify\" " ); break; case Left: startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:text-align=\"left\" " ); break; case Right: startStyleIfNeeded(properties.getType(), properties.getStyleName()); dynamicStyles.append( "fo:text-align=\"right\" " ); break; } } endStyleIfNeeded(); if (styleName == null && properties.getStyleName() != null) { styleName = properties.getStyleName(); } return styleName; } private void setPropertiesKind( ODTStyleProperties propertiesKind ) { this.propertiesKind = propertiesKind; this.hasPropertiesKind = false; } private void startStyleIfNeeded(ContainerType type, String baseStyleName) { if ( this.styleName == null ) { if ( ContainerType.PARAGRAPH.equals( type ) ) { this.styleName = "XDocReport_P" + dynamicParagraphStylesNb++; } else { this.styleName = "XDocReport_T" + dynamicTextStylesNb++; } dynamicStyles.append( "<style:style style:name=\"" ); dynamicStyles.append( styleName ); if (baseStyleName != null) { dynamicStyles.append("\" style:parent-style-name=\""); dynamicStyles.append(baseStyleName); } if (ContainerType.PARAGRAPH.equals(type)) { dynamicStyles.append( "\" style:family=\"paragraph\">" ); } else { dynamicStyles.append( "\" style:family=\"text\">" ); } } if ( !this.hasPropertiesKind ) { switch ( propertiesKind ) { case TEXT: dynamicStyles.append( "<style:text-properties " ); break; case PARAGRAPH: dynamicStyles.append( "/><style:paragraph-properties " ); break; default: break; } this.hasPropertiesKind = true; } } private void endStyleIfNeeded() { if ( this.styleName != null ) { dynamicStyles.append( "/></style:style>" ); } } }