/************************************************************************ * * ParConverter.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-2014 by Henrik Just * * All Rights Reserved. * * Version 1.4 (2014-09-08) * */ package writer2latex.latex; import org.w3c.dom.Element; import writer2latex.latex.util.BeforeAfter; import writer2latex.latex.util.Context; import writer2latex.latex.util.StyleMapItem; import writer2latex.latex.util.StyleMap; import writer2latex.office.OfficeReader; import writer2latex.office.StyleWithProperties; import writer2latex.office.XMLString; import writer2latex.util.Misc; /*
This class converts OpenDocument paragraphs (text:p
) and
* paragraph styles/formatting into LaTeX
Export of formatting depends on the option "formatting":
*ignore_all
* ignore_most
* convert_basic
* convert_most
* convert_all
* TODO: Captions and {foot|end}notes should also use this class */ public class ParConverter extends StyleConverter { private boolean bNeedArrayBslash = false; // Display hidden text? private boolean bDisplayHiddenText = false; /**
Constructs a new ParConverter
.
Process a text:p tag
* @param node The text:p element node containing the paragraph * @param ldp TheLaTeXDocumentPortion
to add LaTeX code to
* @param oc The current context
* @param bLastInBlock If this is true, the paragraph is the
* last one in a block, and we need no trailing blank line (eg. right before
* \end{enumerate}).
*/
public void handleParagraph(Element node, LaTeXDocumentPortion ldp, Context oc, boolean bLastInBlock) {
// Check for display equation (except in table cells)
if ((!oc.isInTable()) && palette.getMathCv().handleDisplayEquation(node,ldp)) { return; }
// Get the style for this paragraph
String sStyleName = node.getAttribute(XMLString.TEXT_STYLE_NAME);
StyleWithProperties style = ofr.getParStyle(sStyleName);
String sDisplayName = ofr.getParStyles().getDisplayName(sStyleName);
// Check for hidden text
if (!bDisplayHiddenText && style!=null && "none".equals(style.getProperty(XMLString.TEXT_DISPLAY))) {
return;
}
// Check for strict handling of styles
if (config.otherStyles()!=LaTeXConfig.ACCEPT && !config.getParStyleMap().contains(sDisplayName)) {
if (config.otherStyles()==LaTeXConfig.WARNING) {
System.err.println("Warning: A paragraph with style "+sDisplayName+" was ignored");
}
else if (config.otherStyles()==LaTeXConfig.ERROR) {
ldp.append("% Error in source document: A paragraph with style ")
.append(palette.getI18n().convert(sDisplayName,false,oc.getLang()))
.append(" was ignored").nl();
}
// Ignore this paragraph:
return;
}
// Empty paragraphs are often (mis)used to achieve vertical spacing in WYSIWYG
// word processors. Hence we translate an empty paragraph to \bigskip.
// This also solves the problem that LaTeX ignores empty paragraphs, Writer doesn't.
// In a well-structured document, an empty paragraph is probably a mistake,
// hence the configuration can specify that it should be ignored.
// Note: Don't use \bigskip in tables (this can lead to strange results)
if (OfficeReader.isWhitespaceContent(node)) {
// Always add page break; other formatting is ignored
BeforeAfter baPage = new BeforeAfter();
palette.getPageSc().applyPageBreak(style,true,baPage);
if (!oc.isInTable()) { ldp.append(baPage.getBefore()); }
if (!config.ignoreEmptyParagraphs()) {
if (!oc.isInTable()) {
ldp.nl().append("\\bigskip").nl();
}
else {
ldp.append("~").nl();
}
if (!bLastInBlock) { ldp.nl(); }
}
if (!oc.isInTable()) { ldp.append(baPage.getAfter()); }
return;
}
Context ic = (Context) oc.clone();
// Always push the font used
palette.getI18n().pushSpecialTable(palette.getCharSc().getFontName(ofr.getParStyle(sStyleName)));
// Apply the style
int nBreakAfter;
BeforeAfter ba = new BeforeAfter();
if (oc.isInTable()) {
nBreakAfter = applyCellParStyle(sStyleName,ba,ic,OfficeReader.getCharacterCount(node)==0,bLastInBlock);
}
else {
nBreakAfter = applyParStyle(sStyleName,ba,ic,OfficeReader.getCharacterCount(node)==0);
}
// Do conversion
ldp.append(ba.getBefore());
palette.getInlineCv().traverseInlineText(node,ldp,ic);
ldp.append(ba.getAfter());
// Add line break if desired
if (nBreakAfter!=StyleMapItem.NONE) { ldp.nl(); }
// Add a blank line except within verbatim and last in a block, and if desired by style map
if (!bLastInBlock && !ic.isVerbatim() && !ic.isInSimpleTable() && nBreakAfter==StyleMapItem.PAR) { ldp.nl(); }
// Flush any pending index marks, reference marks and floating frames
palette.getFieldCv().flushReferenceMarks(ldp,oc);
palette.getIndexCv().flushIndexMarks(ldp,oc);
palette.getDrawCv().flushFloatingFrames(ldp,oc);
// pop the font name
palette.getI18n().popSpecialTable();
}
private int applyCellParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar, boolean bLastInBlock) {
// Paragraph formatting for paragraphs within table cells
// We always use simple par styles here
context.setVerbatim(false);
int nBreakAfter = bLastInBlock ? StyleMapItem.NONE : StyleMapItem.PAR;
if (context.isInSimpleTable()) {
if (config.formatting()!=LaTeXConfig.IGNORE_ALL) {
// only character formatting!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
palette.getI18n().applyLanguage(style,true,true,ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
}
nBreakAfter = StyleMapItem.NONE;
}
else if (config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(sName))) {
// We have a style map in the configuration
StyleMap sm = config.getParStyleMap();
String sDisplayName = ofr.getParStyles().getDisplayName(sName);
String sBefore = sm.getBefore(sDisplayName);
String sAfter = sm.getAfter(sDisplayName);
ba.add(sBefore, sAfter);
// Add line breaks inside?
if (sm.getLineBreak(sDisplayName)) {
if (sBefore.length()>0) { ba.add("\n",""); }
if (sAfter.length()>0 && !"}".equals(sAfter)) { ba.add("","\n"); }
}
nBreakAfter = sm.getBreakAfter(sDisplayName);
if (sm.getVerbatim(sDisplayName)) { context.setVerbatim(true); }
}
else if (bNoTextPar && (config.formatting()==LaTeXConfig.CONVERT_BASIC || config.formatting()==LaTeXConfig.IGNORE_MOST) ) {
// only alignment!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if (bLastInBlock && context.isInLastTableColumn()) { // no grouping needed, but need to fix problem with \\
if ("center".equals(sTextAlign)) { ba.add("\\centering\\arraybslash ",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft\\arraybslash ",""); }
bNeedArrayBslash = true;
}
else if (bLastInBlock) { // no grouping needed
if ("center".equals(sTextAlign)) { ba.add("\\centering ",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft ",""); }
}
else {
if ("center".equals(sTextAlign)) { ba.add("{\\centering ","\\par}"); }
else if ("end".equals(sTextAlign)) { ba.add("{\\raggedleft ","\\par}"); }
nBreakAfter = StyleMapItem.LINE;
}
}
}
else {
// Export character formatting + alignment only
BeforeAfter baPar = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if (bLastInBlock && context.isInLastTableColumn()) { // no grouping needed, but need to fix problem with \\
if ("center".equals(sTextAlign)) { baPar.add("\\centering\\arraybslash",""); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft\\arraybslash",""); }
bNeedArrayBslash = true;
}
else if (bLastInBlock) { // no \par needed
if ("center".equals(sTextAlign)) { baPar.add("\\centering",""); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft",""); }
}
else {
if ("center".equals(sTextAlign)) { baPar.add("\\centering","\\par"); }
else if ("end".equals(sTextAlign)) { baPar.add("\\raggedleft","\\par"); }
}
palette.getI18n().applyLanguage(style,true,true,baText);
palette.getCharSc().applyFont(style,true,true,baText,context);
}
// Group the contents if this is not the last paragraph in the cell
boolean bIsGrouped = false;
if ((!baPar.isEmpty() || !baText.isEmpty()) && !bLastInBlock) {
ba.add("{","}");
bIsGrouped = true;
}
ba.add(baPar);
// Group the text formatting in any case (supertabular needs this)
if (!baText.isEmpty() && !bIsGrouped) {
ba.add("{", "}");
}
ba.add(baText);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
// Update context
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
context.updateFormattingFromStyle(style);
}
return nBreakAfter;
}
/** Use a paragraph style in LaTeX.
* @paramsName
the name of the text style
* @param ba
a BeforeAfter
to put code into
* @param context
the current context. This method will use and update the formatting context
* @param bNoTextPar
true if this paragraph has no text content (hence character formatting is not needed)
*/
private int applyParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar) {
return applyParStyle(sName,ba,context,bNoTextPar,true);
}
private int applyParStyle(String sName, BeforeAfter ba, Context context, boolean bNoTextPar, boolean bBreakInside) {
// No style specified?
if (sName==null) { return StyleMapItem.PAR; }
/*if (context.isInSimpleTable()) {
if (config.formatting()!=LaTeXConfig.IGNORE_ALL) {
// only character formatting!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
palette.getI18n().applyLanguage(style,true,true,ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
if (ba.getBefore().length()>0) { ba.add(" ",""); }
}
}
}
else*/
int nBreakAfter = StyleMapItem.PAR;
if (bNoTextPar && (config.formatting()==LaTeXConfig.CONVERT_BASIC || config.formatting()==LaTeXConfig.IGNORE_MOST) ) {
//TODO: If there is a style map, we should respect that despite the fact that the paragraph is empty
// only alignment!
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if ("center".equals(sTextAlign)) { ba.add("{\\centering ","\\par}"); nBreakAfter = StyleMapItem.LINE; }
else if ("end".equals(sTextAlign)) { ba.add("{\\raggedleft ","\\par}"); nBreakAfter = StyleMapItem.LINE; }
}
}
else {
// Apply the style
if (!styleMap.contains(sName)) { createParStyle(sName); }
String sBefore = styleMap.getBefore(sName);
String sAfter = styleMap.getAfter(sName);
ba.add(sBefore,sAfter);
// Add line breaks inside?
if (bBreakInside && styleMap.getLineBreak(sName)) {
if (sBefore.length()>0) { ba.add("\n",""); }
if (sAfter.length()>0 && !"}".equals(sAfter)) { ba.add("","\n"); }
}
nBreakAfter = styleMap.getBreakAfter(sName);
}
// Update context
StyleWithProperties style = ofr.getParStyle(sName);
if (style!=null) {
context.updateFormattingFromStyle(style);
}
context.setVerbatim(styleMap.getVerbatim(sName));
return nBreakAfter;
}
/** Convert a paragraph style to LaTeX.
*A soft style is declared in styleDeclarations
as
* \newenvironment...
A hard style is used by applying LaTeX code directly
* @paramsName
the OOo name of the style
*/
private void createParStyle(String sName) {
// A paragraph style should always be created relative to main context
Context context = (Context) palette.getMainContext().clone();
// The style may already be declared in the configuration:
String sDisplayName = ofr.getParStyles().getDisplayName(sName);
StyleMap sm = config.getParStyleMap();
if (sm.contains(sDisplayName)) {
styleMap.put(sName,sm.getBefore(sDisplayName),sm.getAfter(sDisplayName),
sm.getLineBreak(sDisplayName),sm.getBreakAfter(sDisplayName),sm.getVerbatim(sDisplayName));
return;
}
// Does the style exist?
StyleWithProperties style = ofr.getParStyle(sName);
if (style==null) {
styleMap.put(sName,"","");
return;
}
// Convert the style!
switch (config.formatting()) {
case LaTeXConfig.CONVERT_MOST:
if (style.isAutomatic()) {
createAutomaticParStyle(style,context);
return;
}
case LaTeXConfig.CONVERT_ALL:
createSoftParStyle(style,context);
return;
case LaTeXConfig.CONVERT_BASIC:
case LaTeXConfig.IGNORE_MOST:
createSimpleParStyle(style,context);
return;
case LaTeXConfig.IGNORE_ALL:
default:
styleMap.put(sName,"","");
}
}
private void createAutomaticParStyle(StyleWithProperties style, Context context) {
// Hard paragraph formatting from this style should be ignored
// (because the user wants to ignore hard paragraph formatting
// or there is a style map for the parent.)
BeforeAfter ba = new BeforeAfter();
BeforeAfter baPar = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply paragraph formatting from parent
// If parent is verbatim, this is all
String sParentName = style.getParentName();
if (styleMap.getVerbatim(sParentName)) {
styleMap.put(style.getName(),styleMap.getBefore(sParentName),styleMap.getAfter(sParentName),
styleMap.getLineBreak(sParentName),styleMap.getBreakAfter(sParentName),styleMap.getVerbatim(sParentName));
return;
}
applyParStyle(sParentName,baPar,context,false,false);
// Apply hard formatting properties:
palette.getPageSc().applyPageBreak(style,false,ba);
palette.getI18n().applyLanguage(style,true,false,baText);
palette.getCharSc().applyFont(style,true,false,baText,context);
// Assemble the bits. If there is any hard character formatting
// we must group the contents.
if (baPar.isEmpty() && !baText.isEmpty()) { ba.add("{","}"); }
else { ba.add(baPar.getBefore(),baPar.getAfter()); }
ba.add(baText.getBefore(),baText.getAfter());
boolean bLineBreak = styleMap.getLineBreak(sParentName);
if (!bLineBreak && !baText.isEmpty()) { ba.add(" ",""); }
styleMap.put(style.getName(),ba.getBefore(),ba.getAfter(),bLineBreak,styleMap.getBreakAfter(sParentName), false);
}
private void createSimpleParStyle(StyleWithProperties style, Context context) {
// Export character formatting + alignment only
if (style.isAutomatic() && config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(style.getParentName()))) {
createAutomaticParStyle(style,context);
return;
}
BeforeAfter ba = new BeforeAfter();
BeforeAfter baText = new BeforeAfter();
// Apply hard formatting attributes
// Note: Left justified text is exported as full justified text!
palette.getPageSc().applyPageBreak(style,false,ba);
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,true);
if ("center".equals(sTextAlign)) { baText.add("\\centering","\\par"); }
else if ("end".equals(sTextAlign)) { baText.add("\\raggedleft","\\par"); }
palette.getI18n().applyLanguage(style,true,true,baText);
palette.getCharSc().applyFont(style,true,true,baText,context);
// Assemble the bits. If there is any hard character formatting
// or alignment we must group the contents.
if (!baText.isEmpty()) { ba.add("{","}"); }
ba.add(baText.getBefore(),baText.getAfter());
styleMap.put(style.getName(),ba.getBefore(),ba.getAfter());
}
private void createSoftParStyle(StyleWithProperties style, Context context) {
// This style should be converted to an enviroment, except if
// it's automatic and there is a config style map for the parent
if (style.isAutomatic() && config.getParStyleMap().contains(ofr.getParStyles().getDisplayName(style.getParentName()))) {
createAutomaticParStyle(style,context);
}
BeforeAfter ba = new BeforeAfter();
applyParProperties(style,ba);
ba.add("\\writerlistparindent\\writerlistleftskip","");
palette.getI18n().applyLanguage(style,true,true,ba);
ba.add("\\leavevmode","");
palette.getCharSc().applyNormalFont(ba);
palette.getCharSc().applyFont(style,true,true,ba,context);
ba.add("\\writerlistlabel","");
ba.add("\\ignorespaces","");
// Declare the paragraph style (\newenvironment)
String sTeXName = "style" + styleNames.getExportName(style.getDisplayName());
styleMap.put(style.getName(),"\\begin{"+sTeXName+"}","\\end{"+sTeXName+"}");
declarations.append("\\newenvironment{").append(sTeXName)
.append("}{").append(ba.getBefore()).append("}{")
.append(ba.getAfter()).append("}").nl();
}
// Remaining methods are private helpers
/** Apply line spacing from a style.
* @paramstyle
the paragraph style to use
* @param ba
a BeforeAfter
to put code into
*/
private void applyLineSpacing(StyleWithProperties style, BeforeAfter ba) {
if (style==null) { return; }
String sLineHeight = style.getProperty(XMLString.FO_LINE_HEIGHT);
if (sLineHeight==null || !sLineHeight.endsWith("%")) { return; }
float fPercent=Misc.getFloat(sLineHeight.substring(0,sLineHeight.length()-1),100);
// Do not allow less that 120% (LaTeX default)
if (fPercent<120) { fPercent = 120; }
ba.add("\\renewcommand\\baselinestretch{"+fPercent/120+"}","");
}
/** Helper: Create a horizontal border. Currently unused
*/ /*private String createBorder(String sLeft, String sRight, String sTop, String sHeight, String sColor) { BeforeAfter baColor = new BeforeAfter(); palette.getColorCv().applyColor(sColor,false,baColor, new Context()); return "{\\setlength\\parindent{0pt}\\setlength\\leftskip{" + sLeft + "}" + "\\setlength\\baselineskip{0pt}\\setlength\\parskip{" + sHeight + "}" + baColor.getBefore() + "\\rule{\\textwidth-" + sLeft + "-" + sRight + "}{" + sHeight + "}" + baColor.getAfter() + "\\par}"; }*/ /**Apply margin+alignment properties from a style.
* @paramstyle
the paragraph style to use
* @param ba
a BeforeAfter
to put code into
*/
private void applyMargins(StyleWithProperties style, BeforeAfter ba) {
// Read padding/margin/indentation properties:
//String sPaddingTop = style.getAbsoluteLength(XMLString.FO_PADDING_TOP);
//String sPaddingBottom = style.getAbsoluteLength(XMLString.FO_PADDING_BOTTOM);
//String sPaddingLeft = style.getAbsoluteLength(XMLString.FO_PADDING_LEFT);
//String sPaddingRight = style.getAbsoluteLength(XMLString.FO_PADDING_RIGHT);
String sMarginTop = style.getAbsoluteLength(XMLString.FO_MARGIN_TOP);
String sMarginBottom = style.getAbsoluteLength(XMLString.FO_MARGIN_BOTTOM);
String sMarginLeft = style.getAbsoluteLength(XMLString.FO_MARGIN_LEFT);
String sMarginRight = style.getAbsoluteLength(XMLString.FO_MARGIN_RIGHT);
String sTextIndent;
if ("true".equals(style.getProperty(XMLString.STYLE_AUTO_TEXT_INDENT))) {
sTextIndent = "2em";
}
else {
sTextIndent = style.getAbsoluteLength(XMLString.FO_TEXT_INDENT);
}
// Read alignment properties:
boolean bRaggedLeft = false; // add 1fil to \leftskip
boolean bRaggedRight = false; // add 1fil to \rightskip
boolean bParFill = false; // add 1fil to \parfillskip
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN);
if ("center".equals(sTextAlign)) {
bRaggedLeft = true; bRaggedRight = true; // centered paragraph
}
else if ("start".equals(sTextAlign)) {
bRaggedRight = true; bParFill = true; // left aligned paragraph
}
else if ("end".equals(sTextAlign)) {
bRaggedLeft = true; // right aligned paragraph
}
else if (!"justify".equals(style.getProperty(XMLString.FO_TEXT_ALIGN_LAST))) {
bParFill = true; // justified paragraph with ragged last line
}
// Create formatting:
String sRubberMarginTop = Misc.multiply("10%",sMarginTop);
if (Misc.length2px(sRubberMarginTop).equals("0")) { sRubberMarginTop="1pt"; }
String sRubberMarginBottom = Misc.multiply("10%",sMarginBottom);
if (Misc.length2px(sRubberMarginBottom).equals("0")) { sRubberMarginBottom="1pt"; }
ba.add("\\setlength\\leftskip{"+sMarginLeft+(bRaggedLeft?" plus 1fil":"")+"}","");
ba.add("\\setlength\\rightskip{"+sMarginRight+(bRaggedRight?" plus 1fil":"")+"}","");
ba.add("\\setlength\\parindent{"+sTextIndent+"}","");
ba.add("\\setlength\\parfillskip{"+(bParFill?"0pt plus 1fil":"0pt")+"}","");
ba.add("\\setlength\\parskip{"+sMarginTop+" plus "+sRubberMarginTop+"}",
"\\unskip\\vspace{"+sMarginBottom+" plus "+sRubberMarginBottom+"}");
}
public void applyAlignment(StyleWithProperties style, boolean bIsSimple, boolean bInherit, BeforeAfter ba) {
if (bIsSimple || style==null) { return; }
String sTextAlign = style.getProperty(XMLString.FO_TEXT_ALIGN,bInherit);
if ("center".equals(sTextAlign)) { ba.add("\\centering",""); }
else if ("start".equals(sTextAlign)) { ba.add("\\raggedright",""); }
else if ("end".equals(sTextAlign)) { ba.add("\\raggedleft",""); }
}
/** Apply all paragraph properties.
* @paramstyle
the paragraph style to use
* @param ba
a BeforeAfter
to put code into
*/
private void applyParProperties(StyleWithProperties style, BeforeAfter ba) {
palette.getPageSc().applyPageBreak(style,true,ba);
ba.add("","\\par");
applyLineSpacing(style,ba);
applyMargins(style,ba);
}
}