/************************************************************************ * * CharStyleConverter.java * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License version 2.1, as published by the Free Software Foundation. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307 USA * * Copyright: 2002-2008 by Henrik Just * * All Rights Reserved. * * Version 1.0 (2008-12-03) * */ package writer2latex.latex; import java.util.Hashtable; import writer2latex.util.*; import writer2latex.office.*; import writer2latex.latex.util.BeforeAfter; import writer2latex.latex.util.Context; import writer2latex.latex.util.StyleMap; /** This class creates LaTeX code from OOo character formatting Character formatting in OOo includes font, font effects/decorations and color. In addition it includes color and language/country information, this is however handled by the classes writer2latex.latex.ColorConverter and writer2latex.latex.style.I18n */ public class CharStyleConverter extends StyleConverter { // Cache of converted font declarations private Hashtable fontDecls = new Hashtable(); // Which formatting should we export? private boolean bIgnoreHardFontsize; private boolean bIgnoreFontsize; private boolean bIgnoreFont; private boolean bIgnoreAll; private boolean bUseUlem; // Do we need actually use ulem.sty or \textsubscript? private boolean bNeedUlem = false; private boolean bNeedSubscript = false; /**

Constructs a new CharStyleConverter.

*/ public CharStyleConverter(OfficeReader ofr, LaTeXConfig config, ConverterPalette palette) { super(ofr,config,palette); bUseUlem = config.useUlem(); // No character formatting at all: bIgnoreAll = config.formatting()==LaTeXConfig.IGNORE_ALL; // No font family or size: bIgnoreFont = config.formatting()<=LaTeXConfig.IGNORE_MOST; // No fontsize: bIgnoreFontsize = config.formatting()<=LaTeXConfig.CONVERT_BASIC; // No hard fontsize bIgnoreHardFontsize = config.formatting()<=LaTeXConfig.CONVERT_MOST; } public void appendDeclarations(LaTeXDocumentPortion pack, LaTeXDocumentPortion decl) { if (bNeedUlem) { pack.append("\\usepackage[normalem]{ulem}").nl(); } if (bNeedSubscript && !config.getTextAttributeStyleMap().contains("subscript")) { decl.append("\\newcommand\\textsubscript[1]{\\ensuremath{{}_{\\text{#1}}}}").nl(); } if (!styleNames.isEmpty()) { decl.append("% Text styles").nl().append(declarations); } } /**

Use a text style in LaTeX.

* @param sName the name of the text style * @param ba a BeforeAfter to put code into */ public void applyTextStyle(String sName, BeforeAfter ba, Context context) { if (sName==null) { return; } String sDisplayName = ofr.getTextStyles().getDisplayName(sName); if (bIgnoreAll) { // Even if all is ignored, we still apply style maps from config.. StyleMap sm = config.getTextStyleMap(); if (sm.contains(sDisplayName)) { ba.add(sm.getBefore(sDisplayName),sm.getAfter(sDisplayName)); } return; } // Style already converted? if (styleMap.contains(sName)) { ba.add(styleMap.getBefore(sName),styleMap.getAfter(sName)); context.updateFormattingFromStyle(ofr.getTextStyle(sName)); // it's verbatim if specified as such in the configuration StyleMap sm = config.getTextStyleMap(); boolean bIsVerbatim = sm.contains(sDisplayName) && sm.getVerbatim(sDisplayName); context.setVerbatim(bIsVerbatim); context.setNoLineBreaks(bIsVerbatim); return; } // The style may already be declared in the configuration: StyleMap sm = config.getTextStyleMap(); if (sm.contains(sDisplayName)) { styleMap.put(sName,sm.getBefore(sDisplayName),sm.getAfter(sDisplayName)); applyTextStyle(sName,ba,context); return; } // Get the style, if it exists: StyleWithProperties style = ofr.getTextStyle(sName); if (style==null) { styleMap.put(sName,"",""); applyTextStyle(sName,ba,context); return; } // Convert automatic style if (style.isAutomatic()) { palette.getI18n().applyLanguage(style,false,true,ba); applyFont(style,false,true,ba,context); applyFontEffects(style,true,ba); context.updateFormattingFromStyle(ofr.getTextStyle(sName)); return; } // Convert soft style: // This must be converted relative to a blank context! BeforeAfter baText = new BeforeAfter(); palette.getI18n().applyLanguage(style,false,true,baText); applyFont(style,false,true,baText,new Context()); applyFontEffects(style,true,baText); // declare the text style (\newcommand) String sTeXName = styleNames.getExportName(ofr.getTextStyles().getDisplayName(sName)); styleMap.put(sName,"\\textstyle"+sTeXName+"{","}"); declarations.append("\\newcommand\\textstyle") .append(sTeXName).append("[1]{") .append(baText.getBefore()).append("#1").append(baText.getAfter()) .append("}").nl(); applyTextStyle(sName,ba,context); } public String getFontName(StyleWithProperties style) { if (style!=null) { String sName = style.getProperty(XMLString.STYLE_FONT_NAME); if (sName!=null) { FontDeclaration fd = ofr.getFontDeclaration(sName); if (fd!=null) { return fd.getFontFamily(); } } } return null; } // Get the font name from a char style public String getFontName(String sStyleName) { return getFontName(ofr.getTextStyle(sStyleName)); } /**

Apply hard character formatting (no inheritance).

*

This is used in sections and {foot|end}notes

* @param style the style to use * @param ba the BeforeAfter to add LaTeX code to */ public void applyHardCharFormatting(StyleWithProperties style, BeforeAfter ba) { palette.getI18n().applyLanguage(style,true,false,ba); applyFont(style,true,false,ba,new Context()); if (!ba.isEmpty()) { ba.add(" ",""); } } /**

Apply all font attributes (family, series, shape, size and color).

* @param style the OOo style to read attributesfrom * @param bDecl true if declaration form is required * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ public void applyFont(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) { // Note: if bDecl is true, nothing will be put in the "after" part of ba. if (style==null) { return; } applyNfssSize(style,bDecl,bInherit,ba,context); applyNfssFamily(style,bDecl,bInherit,ba,context); applyNfssSeries(style,bDecl,bInherit,ba,context); applyNfssShape(style,bDecl,bInherit,ba,context); palette.getColorCv().applyColor(style,bDecl,bInherit,ba,context); } /**

Reset to normal font, size and color.

* @param ba the BeforeAfter to add LaTeX code to. */ public void applyNormalFont(BeforeAfter ba) { ba.add("\\normalfont\\normalsize",""); palette.getColorCv().applyNormalColor(ba); } /**

Apply default font attributes (family, series, shape, size and color).

* @param style the OOo style to read attributesfrom * @param ldp the LaTeXDocumentPortion to add LaTeX code to. */ public void applyDefaultFont(StyleWithProperties style, LaTeXDocumentPortion ldp) { if (style==null) { return; } String s = convertFontDeclaration(style.getProperty(XMLString.STYLE_FONT_NAME)); if (s!=null){ ldp.append("\\renewcommand\\familydefault{\\") .append(s).append("default}").nl(); } // TODO: Else read props directly from the style s = nfssSeries(style.getProperty(XMLString.FO_FONT_WEIGHT)); if (s!=null) { ldp.append("\\renewcommand\\seriesdefault{\\") .append(s).append("default}").nl(); } s = nfssShape(style.getProperty(XMLString.FO_FONT_VARIANT), style.getProperty(XMLString.FO_FONT_STYLE)); if (s!=null) { ldp.append("\\renewcommand\\shapedefault{\\") .append(s).append("default}").nl(); } palette.getColorCv().setNormalColor(style.getProperty(XMLString.FO_COLOR),ldp); } /**

Apply font effects (position, underline, crossout, change case.

* @param style the OOo style to read attributesfrom * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ public void applyFontEffects(StyleWithProperties style, boolean bInherit, BeforeAfter ba) { if (style==null) { return; } applyTextPosition(style, bInherit, ba); applyUnderline(style, bInherit, ba); applyCrossout(style, bInherit, ba); applyChangeCase(style, bInherit, ba); } // Remaining methods are private /**

Apply font family.

* @param style the OOo style to read attributesfrom * @param bDecl true if declaration form is required * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyNfssFamily(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) { // Note: if bDecl is true, nothing will be put in the "after" part of ba. if (style==null || bIgnoreFont) { return; } String sFontName=style.getProperty(XMLString.STYLE_FONT_NAME,bInherit); if (sFontName!=null){ String sFamily = convertFontDeclaration(sFontName); if (sFamily==null) { return; } if (sFamily.equals(convertFontDeclaration(context.getFontName()))) { return; } if (bDecl) { ba.add("\\"+sFamily+"family",""); } else { ba.add("\\text"+sFamily+"{","}"); } } // TODO: Else read props directly from the style } /**

Apply font series.

* @param style the OOo style to read attributesfrom * @param bDecl true if declaration form is required * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyNfssSeries(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) { // Note: if bDecl is true, nothing will be put in the "after" part of ba. if (style!=null && !bIgnoreAll) { String sSeries = nfssSeries(style.getProperty(XMLString.FO_FONT_WEIGHT,bInherit)); if (sSeries!=null) { // Temporary: Support text-attribute style maps for this particular case // TODO: Reimplement the CharStyleConverter to properly support this... if (!bDecl && "bf".equals(sSeries) && config.getTextAttributeStyleMap().contains("bold")) { ba.add(config.getTextAttributeStyleMap().getBefore("bold"), config.getTextAttributeStyleMap().getAfter("bold")); } else { if (style.isAutomatic()) { // optimize hard formatting if (sSeries.equals(nfssSeries(context.getFontWeight()))) { return; } if (context.getFontWeight()==null && sSeries.equals("md")) { return; } } if (bDecl) { ba.add("\\"+sSeries+"series",""); } else { ba.add("\\text"+sSeries+"{","}"); } } } } } /**

Apply font shape.

* @param style the OOo style to read attributesfrom * @param bDecl true if declaration form is required * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyNfssShape(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) { // Note: if bDecl is true, nothing will be put in the "after" part of ba. if (style!=null && !bIgnoreAll) { String sVariant = style.getProperty(XMLString.FO_FONT_VARIANT, bInherit); String sStyle = style.getProperty(XMLString.FO_FONT_STYLE, bInherit); String sShape = nfssShape(sVariant,sStyle); if (sShape!=null) { // Temporary: Support text-attribute style maps for this particular case // TODO: Reimplement the CharStyleConverter to properly support this... if (!bDecl && "sc".equals(sShape) && config.getTextAttributeStyleMap().contains("small-caps")) { ba.add(config.getTextAttributeStyleMap().getBefore("small-caps"), config.getTextAttributeStyleMap().getAfter("small-caps")); } else if (!bDecl && "it".equals(sShape) && config.getTextAttributeStyleMap().contains("italic")) { ba.add(config.getTextAttributeStyleMap().getBefore("italic"), config.getTextAttributeStyleMap().getAfter("italic")); } else { if (style.isAutomatic()) { // optimize hard formatting if (sShape.equals(nfssShape(context.getFontVariant(),context.getFontStyle()))) return; if (context.getFontVariant()==null && context.getFontStyle()==null && sShape.equals("up")) return; } if (bDecl) ba.add("\\"+sShape+"shape",""); else ba.add("\\text"+sShape+"{","}"); } } } } /**

Apply font size.

* @param style the OOo style to read attributesfrom * @param bDecl true if declaration form is required * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyNfssSize(StyleWithProperties style, boolean bDecl, boolean bInherit, BeforeAfter ba, Context context) { // Note: if bDecl is true, nothing will be put in the "after" part of ba. if (style==null|| bIgnoreFontsize || (bIgnoreHardFontsize && style.isAutomatic())) { return; } if (style.getProperty(XMLString.FO_FONT_SIZE, bInherit)==null) { return; } String sSize = nfssSize(style.getAbsoluteProperty(XMLString.FO_FONT_SIZE)); if (sSize==null) { return; } if (sSize.equals(nfssSize(context.getFontSize()))) { return; } if (bDecl) { ba.add(sSize,""); } else { ba.add("{"+sSize+" ","}"); } } // Remaining methods are not context-sensitive /**

Apply text position.

* @param style the OOo style to read attributesfrom * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyTextPosition(StyleWithProperties style, boolean bInherit, BeforeAfter ba) { if (style!=null && !bIgnoreAll) { String s = textPosition(style.getProperty(XMLString.STYLE_TEXT_POSITION, bInherit)); // Temporary: Support text-attribute style maps for this particular case // TODO: Reimplement the CharStyleConverter to properly support this... if (config.getTextAttributeStyleMap().contains("superscript") && "\\textsuperscript".equals(s)) { ba.add(config.getTextAttributeStyleMap().getBefore("superscript"), config.getTextAttributeStyleMap().getAfter("superscript")); } else if (config.getTextAttributeStyleMap().contains("subscript") && "\\textsubscript".equals(s)) { ba.add(config.getTextAttributeStyleMap().getBefore("subscript"), config.getTextAttributeStyleMap().getAfter("subscript")); } else if (s!=null) { ba.add(s+"{","}"); } } } /**

Apply text underline.

* @param style the OOo style to read attributesfrom * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyUnderline(StyleWithProperties style, boolean bInherit, BeforeAfter ba) { if (style==null || !bUseUlem) { return; } if (bIgnoreAll) { return; } String sTag = ofr.isOpenDocument() ? XMLString.STYLE_TEXT_UNDERLINE_STYLE : XMLString.STYLE_TEXT_UNDERLINE; String s = underline(style.getProperty(sTag, bInherit)); if (s!=null) { bNeedUlem = true; ba.add(s+"{","}"); } } /**

Apply text crossout.

* @param style the OOo style to read attributesfrom * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyCrossout(StyleWithProperties style, boolean bInherit, BeforeAfter ba) { if (style==null || !bUseUlem) { return; } if (bIgnoreAll) { return; } String sTag = ofr.isOpenDocument() ? XMLString.STYLE_TEXT_LINE_THROUGH_STYLE : XMLString.STYLE_TEXT_CROSSING_OUT; String s = crossout(style.getProperty(sTag, bInherit)); if (s!=null) { bNeedUlem = true; ba.add(s+"{","}"); } } /**

Apply change case.

* @param style the OOo style to read attributesfrom * @param bInherit true if inherited properties should be used * @param ba the BeforeAfter to add LaTeX code to. */ private void applyChangeCase(StyleWithProperties style, boolean bInherit, BeforeAfter ba) { if (style==null) { return; } if (bIgnoreAll) { return; } String s = changeCase(style.getProperty(XMLString.FO_TEXT_TRANSFORM)); if (s!=null) { ba.add(s+"{","}"); } } /**

Convert font declarations to LaTeX.

*

It returns a generic LaTeX font family (rm, tt, sf).

*

It returns null if the font declaration doesn't exist.

* @param sName the name of the font declaration * @return String with a LaTeX generic fontfamily */ private String convertFontDeclaration(String sName) { FontDeclaration fd = ofr.getFontDeclaration(sName); if (fd==null) { return null; } if (!fontDecls.containsKey(sName)) { String sFontFamily = fd.getFontFamily(); String sFontPitch = fd.getFontPitch(); String sFontFamilyGeneric = fd.getFontFamilyGeneric(); fontDecls.put(sName,nfssFamily(sFontFamily,sFontFamilyGeneric,sFontPitch)); } return fontDecls.get(sName); } // The remaining methods are static helpers to convert single style properties // Font change. These methods return the declaration form if the paramater // bDecl is true, and otherwise the command form private static final String nfssFamily(String sFontFamily, String sFontFamilyGeneric, String sFontPitch){ // Note: Defaults to rm // TODO: What about decorative, script, system? if ("fixed".equals(sFontPitch)) return "tt"; else if ("modern".equals(sFontFamilyGeneric)) return "tt"; else if ("swiss".equals(sFontFamilyGeneric)) return "sf"; else return "rm"; } private static final String nfssSeries(String sFontWeight){ if (sFontWeight==null) return null; if ("bold".equals(sFontWeight)) return "bf"; else return "md"; } private static final String nfssShape(String sFontVariant, String sFontStyle){ if (sFontVariant==null && sFontStyle==null) return null; if ("small-caps".equals(sFontVariant)) return "sc"; else if ("italic".equals(sFontStyle)) return "it"; else if ("oblique".equals(sFontStyle)) return "sl"; else return "up"; } private static final String nfssSize(String sFontSize){ if (sFontSize==null) return null; return "\\fontsize{"+sFontSize+"}{"+Misc.multiply("120%",sFontSize)+"}\\selectfont"; } // other character formatting private final String textPosition(String sTextPosition){ if (sTextPosition==null) return null; if (sTextPosition.startsWith("super")) return "\\textsuperscript"; if (sTextPosition.startsWith("sub") || sTextPosition.startsWith("-")) { bNeedSubscript = true; return "\\textsubscript"; } if (sTextPosition.startsWith("0%")) return null; return "\\textsuperscript"; } private static final String underline(String sUnderline) { if (sUnderline==null) { return null; } if (sUnderline.equals("none")) { return null; } if (sUnderline.indexOf("wave")>=0) { return "\\uwave"; } return "\\uline"; } private static final String crossout(String sCrossout) { if (sCrossout==null) { return null; } if (sCrossout.equals("X")) { return "\\xout"; } if (sCrossout.equals("slash")) { return "\\xout"; } return "\\sout"; } private static final String changeCase(String sTextTransform){ if ("lowercase".equals(sTextTransform)) return "\\MakeLowercase"; if ("uppercase".equals(sTextTransform)) return "\\MakeUppercase"; return null; } }