1 /* This file is part of the Calligra project
2 
3    Copyright (C) 2009 Benjamin Cail <cricketc@gmail.com>
4    Copyright (C) 2010-2012 Matus Uzak <matus.uzak@gmail.com>
5 
6    This library is free software; you can redistribute it and/or
7    modify it under the terms of the Library GNU General Public
8    version 2 of the License, or (at your option) version 3 or,
9    at the discretion of KDE e.V (which shall act as a proxy as in
10    section 14 of the GPLv3), any later version..
11 
12    This library is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15    Library General Public License for more details.
16 
17    You should have received a copy of the GNU Library General Public License
18    along with this library; see the file COPYING.LIB.  If not, write to
19    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
20    Boston, MA 02110-1301, USA.
21 
22 */
23 
24 #include "paragraph.h"
25 
26 #include <math.h>
27 
28 #include "conversion.h"
29 #include "msdoc.h"
30 #include "MsDocDebug.h"
31 
32 //define the static attribute
33 QStack<QString> Paragraph::m_bgColors;
34 
35 //definition of local functions
36 const char* getStrokeValue(const uint brcType);
37 const char* getTextUnderlineMode(const uint kul);
38 const char* getTextUnderlineStyle(const uint kul);
39 const char* getTextUnderlineType(const uint kul);
40 const char* getTextUnderlineWidth(const uint kul);
41 
42 
Paragraph(KoGenStyles * mainStyles,const QString & bgColor,bool inStylesDotXml,bool isHeading,bool inHeaderFooter,int outlineLevel)43 Paragraph::Paragraph(KoGenStyles* mainStyles, const QString& bgColor, bool inStylesDotXml, bool isHeading,
44                      bool inHeaderFooter, int outlineLevel)
45         : m_paragraphProperties(0),
46         m_paragraphProperties2(0),
47         m_characterProperties(0),
48         m_odfParagraphStyle(0),
49         m_odfParagraphStyle2(0),
50         m_mainStyles(0),
51         m_paragraphStyle(0),
52         m_paragraphStyle2(0),
53         m_inStylesDotXml(inStylesDotXml),
54         m_isHeading(isHeading),
55         m_inHeaderFooter(inHeaderFooter),
56         m_outlineLevel(0),
57         m_dropCapStatus(NoDropCap),
58         m_containsPageNumberField(false),
59         m_combinedCharacters(false)
60 {
61     debugMsDoc;
62     m_mainStyles = mainStyles;
63     m_odfParagraphStyle = new KoGenStyle(KoGenStyle::ParagraphAutoStyle, "paragraph");
64 
65     if (inStylesDotXml) {
66         debugMsDoc << "this paragraph is in styles.xml";
67         m_odfParagraphStyle->setAutoStyleInStylesDotXml(true);
68         m_inStylesDotXml = true;
69     }
70 
71     if (isHeading)     {
72         debugMsDoc << "this paragraph is a heading";
73         m_outlineLevel = (outlineLevel > 0 ? outlineLevel : 1);
74     } else {
75         m_outlineLevel = -1;
76     }
77 
78     //init the background-color stack to page background-color
79     if (m_bgColors.size() > 0) {
80         warnMsDoc << "BUG: m_bgColors stack NOT empty, clearing!";
81         m_bgColors.clear();
82     }
83 
84     if (!bgColor.isEmpty()) {
85         pushBgColor(bgColor);
86     } else {
87         warnMsDoc << "Warning: page background-color information missing!";
88     }
89 }
90 
~Paragraph()91 Paragraph::~Paragraph()
92 {
93     delete m_odfParagraphStyle;
94     m_odfParagraphStyle = 0;
95 
96     m_bgColors.clear();
97 }
98 
setParagraphStyle(const wvWare::Style * paragraphStyle)99 void Paragraph::setParagraphStyle(const wvWare::Style* paragraphStyle)
100 {
101     m_paragraphStyle = paragraphStyle;
102     m_odfParagraphStyle->addAttribute("style:parent-style-name",
103                                       Conversion::styleName2QString(m_paragraphStyle->name()));
104 }
105 
setParagraphProperties(wvWare::SharedPtr<const wvWare::ParagraphProperties> props)106 void Paragraph::setParagraphProperties(wvWare::SharedPtr<const wvWare::ParagraphProperties> props)
107 {
108     m_paragraphProperties = props;
109 
110     const wvWare::Word97::PAP* refPap = 0;
111     if (m_paragraphStyle) {
112         refPap = &m_paragraphStyle->paragraphProperties().pap();
113     }
114     const wvWare::Word97::PAP& pap = props->pap();
115 
116     QString color;
117 
118     //process the background-color information in order to calculate a
119     //proper fo:color for the text in case of cvAuto.
120     if (!refPap ||
121         (refPap->shd.cvBack != pap.shd.cvBack) ||
122         (refPap->shd.isShdAuto() != pap.shd.isShdAuto()) ||
123         (refPap->shd.isShdNil() != pap.shd.isShdNil()))
124     {
125         color = Conversion::shdToColorStr(pap.shd, currentBgColor(), QString());
126     }
127     //Check the background-color of the named paragraph style.
128     else {
129         const KoGenStyle *pStyle = m_mainStyles->style(Conversion::styleName2QString(m_paragraphStyle->name()), m_paragraphStyle->type() == sgcPara ? "paragraph" : "text");
130         if (pStyle) {
131             color = pStyle->property("fo:background-color", KoGenStyle::ParagraphType);
132             if (color.isEmpty() || color == "transparent") {
133                 color = pStyle->property("fo:background-color", KoGenStyle::TextType);
134             }
135             if (color == "transparent") {
136                 color.clear();
137             }
138         }
139     }
140 
141     if (!color.isEmpty()) {
142         pushBgColor(color);
143     }
144 }
145 
popBgColor(void)146 void Paragraph::popBgColor(void)
147 {
148     if (m_bgColors.isEmpty()) {
149         warnMsDoc << "Warning: m_bgColors stack already empty!";
150     } else {
151         m_bgColors.pop();
152     }
153 }
154 
updateBgColor(const QString & val)155 void Paragraph::updateBgColor(const QString& val)
156 {
157     if (!m_bgColors.isEmpty()) {
158         m_bgColors.pop();
159     }
160     m_bgColors.push(val);
161 }
162 
addRunOfText(QString text,wvWare::SharedPtr<const wvWare::Word97::CHP> chp,const QString & fontName,const wvWare::StyleSheet & styles,bool addCompleteElement)163 void Paragraph::addRunOfText(QString text, wvWare::SharedPtr<const wvWare::Word97::CHP> chp,
164                              const QString &fontName, const wvWare::StyleSheet& styles,
165                              bool addCompleteElement)
166 {
167     // Check for column break in this text string
168     int colBreak = text.indexOf(QChar(0xE));
169 
170     //I think this break always comes at the beginning of the text string
171     if (colBreak == 0) {
172         // Add needed attribute to paragraph style.
173         //
174         // NOTE: This logic breaks down if this isn't the first string in the
175         //       paragraph, or there are other strings with another colBreak
176         //       later in the same paragraph.
177         m_odfParagraphStyle->addProperty("fo:break-before", "column", KoGenStyle::ParagraphType);
178         // Remove character that signaled a column break
179         text.remove(QChar(0xE));
180     }
181 
182     // if it's inner paragraph, push back true this is an
183     // m_textStyles.push_back(nullptr) complement if we still need the style
184     // applied
185     m_addCompleteElement.push_back(addCompleteElement);
186 
187     // Add text string to list.
188     //m_textStrings.push_back(QString(text));
189     m_textStrings.append(QString(text));
190 
191     // Now find out what style to associate with the string.
192 
193     if (chp == 0) {
194         // if inner paragraph - just add a null style & return from function
195         KoGenStyle* style = 0;
196         m_textStyles.push_back(style);
197         return;
198     }
199 
200     const wvWare::Style* msTextStyle = styles.styleByIndex(chp->istd);
201     if (!msTextStyle && styles.size()) {
202         msTextStyle = styles.styleByID(stiNormalChar);
203         debugMsDoc << "Invalid reference to text style, reusing NormalChar";
204     }
205     Q_ASSERT(msTextStyle);
206 
207     QString msTextStyleName = Conversion::styleName2QString(msTextStyle->name());
208     debugMsDoc << "text based on characterstyle " << msTextStyleName;
209 
210     KoGenStyle *textStyle = 0;
211 
212     bool suppresFontSize = false;
213     if (m_textStyles.size() == 0 && m_paragraphProperties->pap().dcs.lines > 1) {
214         suppresFontSize = true;
215     }
216 
217     // Apply any extra properties to the auto style.  Those extra properties
218     // are the diff beteween the referenceChp and the summed chp.  ReferenceCHP
219     // can be either from the paragraph style or the character style.
220     if (msTextStyle->sti() != stiNormalChar) {
221         // this is not the default character style
222         textStyle = new KoGenStyle(KoGenStyle::TextAutoStyle, "text");
223         if (m_inStylesDotXml) {
224             textStyle->setAutoStyleInStylesDotXml(true);
225         }
226         textStyle->setParentName(msTextStyleName);
227         const wvWare::Word97::CHP* refChp = &msTextStyle->chp();
228 
229         //if we have a new font, process that
230         if (!refChp || refChp->ftcAscii != chp->ftcAscii) {
231             if (!fontName.isEmpty()) {
232                 textStyle->addProperty(QString("style:font-name"), fontName, KoGenStyle::TextType);
233             }
234             applyCharacterProperties(chp, textStyle, msTextStyle, suppresFontSize, m_combinedCharacters);
235         }
236     } else {
237         // Default Paragraph Font, which is handled differently
238         // Meaning we should really compare against the CHP of the paragraph
239         textStyle = new KoGenStyle(KoGenStyle::TextAutoStyle, "text");
240         if (m_inStylesDotXml) {
241             textStyle->setAutoStyleInStylesDotXml(true);
242         }
243         //if we have a new font, process that
244         const wvWare::Word97::CHP* refChp = &m_paragraphStyle->chp();
245         if (!refChp || refChp->ftcAscii != chp->ftcAscii) {
246             if (!fontName.isEmpty()) {
247                 textStyle->addProperty(QString("style:font-name"), fontName, KoGenStyle::TextType);
248             }
249         }
250         applyCharacterProperties(chp, textStyle, m_paragraphStyle, suppresFontSize, m_combinedCharacters);
251     }
252 
253     //add text style to list
254     m_textStyles.push_back(textStyle);
255 }
256 
writeToFile(KoXmlWriter * writer,bool openTextBox,QChar * tabLeader)257 QString Paragraph::writeToFile(KoXmlWriter* writer, bool openTextBox, QChar* tabLeader)
258 {
259     debugMsDoc;
260 
261     //TODO: The paragraph-properties might have to be set before
262     //text-properties to have proper automatic colors.
263 
264     //[MS-DOC] PAP -> [ODF] paragraph-properties
265     applyParagraphProperties(*m_paragraphProperties, m_odfParagraphStyle, m_paragraphStyle,
266                              m_inHeaderFooter && m_containsPageNumberField, this, tabLeader);
267 
268     // text-properties are required for empty paragraphs or paragraphs
269     // containing only floating/inline objects.  If there is any text chunk,
270     // then the text-properties are included in the style of type TextStyle.
271     bool textPropsRequired = false;
272     for (int i = 0; i < m_textStyles.size(); i++) {
273         if (!m_textStyles[i]) {
274             textPropsRequired = true;
275             break;
276         }
277     }
278     // [MS-DOC] CHP -> [ODF] text-properties
279     if (m_textStrings.isEmpty() || textPropsRequired) {
280         if (m_characterProperties) {
281             applyCharacterProperties(m_characterProperties, m_odfParagraphStyle, m_paragraphStyle);
282         } else {
283             debugMsDoc << "Missing CHPs for an empty paragraph!";
284         }
285     }
286 
287     // MS Word puts dropcap characters in its own paragraph with the
288     // rest of the text in the subsequent paragraph. On the other
289     // hand, ODF wants the whole paragraph to be one unit.
290     //
291     // So if this paragraph is only one string and we have a dropcap,
292     // then we should combine this paragraph with the next, and write
293     // them together.  That means we should just return here now so
294     // that the next call to writeToFile() they will be in the same
295     // paragraph.
296     if (m_dropCapStatus == IsDropCapPara) {
297         debugMsDoc << "returning with drop cap paragraph";
298         if (m_textStrings.size()) {
299             if (m_textStyles[0] != 0) {
300                 m_dropCapStyleName= m_textStyles[0]->parentName();
301             }
302         }
303         return QString();
304     }
305 
306     // If there is a dropcap defined, then write it to the style.
307     if (m_dropCapStatus == HasDropCapIntegrated) {
308         debugMsDoc << "Creating drop cap style";
309 
310         QBuffer buf;
311         buf.open(QIODevice::WriteOnly);
312         KoXmlWriter tmpWriter(&buf, 3);
313         tmpWriter.startElement("style:drop-cap");
314         tmpWriter.addAttribute("style:lines", m_dcs_lines);
315         tmpWriter.addAttributePt("style:distance", m_dropCapDistance);
316         // We have only support for fdct=1, i.e. regular dropcaps.
317         // There is no support for fdct=2, i.e. dropcaps in the margin (ODF doesn't support this).
318         tmpWriter.addAttribute("style:length", m_dcs_fdct > 0 ? 1 : 0);
319         if (!m_dropCapStyleName.isEmpty()) {
320             tmpWriter.addAttribute("style:style-name", m_dropCapStyleName.toUtf8());
321         }
322         tmpWriter.endElement(); //style:drop-cap
323         buf.close();
324 
325         QString contents = QString::fromUtf8(buf.buffer(), buf.buffer().size());
326         m_odfParagraphStyle->addChildElement("style:drop-cap", contents);
327 
328         m_dropCapStatus = NoDropCap;
329     }
330 
331     QString textStyleName;
332     //add paragraph style to the collection and its name to the content
333     debugMsDoc << "adding paragraphStyle";
334     //add the attribute for our style in <text:p>
335     textStyleName = "P";
336     textStyleName = m_mainStyles->insert(*m_odfParagraphStyle, textStyleName);
337 
338     //check if the paragraph is inside of an absolutely positioned frame
339     if (openTextBox) {
340         KoGenStyle gs(KoGenStyle::GraphicAutoStyle, "graphic");
341         const KoGenStyle::PropertyType gt = KoGenStyle::GraphicType;
342         QString drawStyleName;
343 
344         writer->startElement("text:p", false);
345         writer->addAttribute("text:style-name", textStyleName.toUtf8());
346 
347         const wvWare::Word97::PAP& pap = m_paragraphProperties->pap();
348         int dxaAbs = 0;
349         int dyaAbs = 0;
350 
351         // horizontal position of the anchor
352         QString pos = Conversion::getHorizontalPos(pap.dxaAbs);
353         gs.addProperty("style:horizontal-pos", pos, gt);
354         if (pos == "from-left") {
355             dxaAbs = pap.dxaAbs;
356         }
357         // vertical position of the anchor
358         pos = Conversion::getVerticalPos(pap.dyaAbs);
359         gs.addProperty("style:vertical-pos", pos, gt);
360         if (pos == "from-top") {
361             dyaAbs = pap.dyaAbs;
362         }
363         // relative vertical position of the anchor
364         QString anchor = Conversion::getVerticalRel(pap.pcVert);
365         if (!anchor.isEmpty()) {
366             gs.addProperty("style:vertical-rel", anchor, gt);
367         }
368         // relative horizontal position of the anchor
369         anchor = Conversion::getHorizontalRel(pap.pcHorz);
370         if (!anchor.isEmpty()) {
371             gs.addProperty("style:horizontal-rel", anchor, gt);
372         }
373 
374         if (pap.dxaWidth == 0) {
375             gs.addProperty("draw:auto-grow-width", "true");
376         }
377         if (pap.dyaHeight == 0) {
378             gs.addProperty("draw:auto-grow-height", "true");
379         }
380 
381         // as fas as can be determined
382         gs.addProperty("style:flow-with-text", "false");
383 
384         // In case a header/footer is processed, save the style into styles.xml
385         if (m_inStylesDotXml) {
386             gs.setAutoStyleInStylesDotXml(true);
387         }
388 
389         //TODO: improve frame borders support
390         if ( pap.brcLeft.brcType || pap.brcTop.brcType ||
391              pap.brcRight.brcType || pap.brcBottom.brcType )
392         {
393             debugMsDoc << "Frame bordes not fully supported!";
394         }
395         gs.addProperty("draw:stroke", getStrokeValue(pap.brcLeft.brcType), gt);
396 
397         drawStyleName = "fr";
398         drawStyleName = m_mainStyles->insert(gs, drawStyleName);
399         writer->startElement("draw:frame");
400         writer->addAttribute("draw:style-name", drawStyleName.toUtf8());
401         writer->addAttribute("text:anchor-type", "paragraph");
402 
403         if (pap.dxaWidth != 0) {
404             writer->addAttributePt("svg:width", (double)pap.dxaWidth / 20);
405         }
406         if (pap.dyaHeight != 0) {
407             writer->addAttributePt("svg:height", (double)pap.dyaHeight / 20);
408         }
409         writer->addAttributePt("svg:x", (double)dxaAbs / 20);
410         writer->addAttributePt("svg:y", (double)dyaAbs / 20);
411         writer->startElement("draw:text-box");
412     }
413 
414     // Open paragraph or heading tag.
415     if (m_isHeading) {
416         writer->startElement("text:h", false);
417         writer->addAttribute("text:outline-level", m_outlineLevel);
418     } else {
419         writer->startElement("text:p", false);
420     }
421 
422     writer->addAttribute("text:style-name", textStyleName.toUtf8());
423 
424     //TODO: insert <text:tab> elements at specified locations
425 
426     //if there's any paragraph content
427     if (!m_textStrings.isEmpty()) {
428         //Loop through each text strings and styles (equal # of both) and write
429         //them to the file.
430         debugMsDoc << "writing text spans now";
431         QString oldStyleName;
432         bool startedSpan = false;
433         for (int i = 0; i < m_textStrings.size(); i++) {
434             if (m_textStyles[i] == 0) {
435                 if (startedSpan) {
436                     writer->endElement(); //text:span
437                     startedSpan = false;
438                     oldStyleName = " ";
439                 }
440                 //if style is null, we have an inner paragraph and add the
441                 //complete paragraph element to writer need to get const char*
442                 //from the QString
443                 debugMsDoc << "complete element: " <<
444                                  m_textStrings[i].toLocal8Bit().constData();
445                 writer->addCompleteElement(m_textStrings[i].toUtf8().constData());
446             } else {
447                 //put style into m_mainStyles & get its name
448                 textStyleName = 'T';
449                 textStyleName = m_mainStyles->insert(*m_textStyles[i], textStyleName);
450                 //debugMsDoc << m_textStyles[i]->type();
451 
452                 if (oldStyleName != textStyleName) {
453                     if (startedSpan) {
454                         writer->endElement(); //text:span
455                         startedSpan = false;
456                     }
457                     if (textStyleName != "DefaultParagraphFont") {
458                         writer->startElement("text:span");
459                         writer->addAttribute("text:style-name", textStyleName.toUtf8());
460                         startedSpan = true;
461                     }
462                     oldStyleName = textStyleName;
463                 }
464                 //Write text string to writer.  Now I just need to write the
465                 //text:span to the header tag.
466                 debugMsDoc << "Writing \"" << m_textStrings[i] << "\"";
467                 if (m_addCompleteElement[i] == false) {
468                     writer->addTextSpan(m_textStrings[i]);
469                 }
470                 //special case we need style applied and complete element added
471                 else {
472                     writer->addCompleteElement(m_textStrings[i].toUtf8().constData());
473                 }
474                 //cleanup
475                 delete m_textStyles[i];
476                 m_textStyles[i] = 0;
477             }
478         }
479         // If we have an unfinished text:span, finish it now.
480         if (startedSpan) {
481             writer->endElement(); //text:span
482             startedSpan = false;
483         }
484     } //if (!m_textStrings.isEmpty())
485 
486     //close the <text:p> or <text:h> tag we opened
487     writer->endElement();
488 
489     return textStyleName;
490 }
491 
492 // open/closeInnerParagraph: enables us to process a paragraph inside
493 // another paragraph, as in the case of footnotes.  openInnerParagraph
494 // should be called from texthandler.paragraphStart, and
495 // closeInnerParagraph should be called from paragraphEnd, but only
496 // after calling writeToFile to write the content to another xml
497 // writer (eg. m_footnoteWriter).
openInnerParagraph()498 void Paragraph::openInnerParagraph()
499 {
500     debugMsDoc;
501 
502     //copy parent and paragraph styles
503     m_odfParagraphStyle2 = m_odfParagraphStyle;
504     m_odfParagraphStyle = new KoGenStyle(KoGenStyle::ParagraphAutoStyle, "paragraph");
505     m_paragraphStyle2 = m_paragraphStyle;
506     m_paragraphProperties2 = m_paragraphProperties;
507     m_paragraphProperties = 0;
508 
509     //move m_textStyles and m_textStrings content to
510     //m_textStyles2 and m_textStrings2
511     m_textStyles2 = m_textStyles;
512     m_textStrings2 = m_textStrings;
513     m_addCompleteElement2 = m_addCompleteElement;
514     m_textStyles.clear();
515     m_textStrings.clear();
516     m_addCompleteElement.clear();
517 }
518 
closeInnerParagraph()519 void Paragraph::closeInnerParagraph()
520 {
521     debugMsDoc;
522 
523     //clear temp variables and restore originals
524     delete m_odfParagraphStyle;
525     m_odfParagraphStyle = m_odfParagraphStyle2;
526     m_odfParagraphStyle2 = 0;
527     m_paragraphStyle = m_paragraphStyle2;
528     m_paragraphStyle2 = 0;
529     m_paragraphProperties = m_paragraphProperties2;
530     m_paragraphProperties2 = 0;
531     m_textStyles.clear();
532     m_textStrings.clear();
533     m_addCompleteElement.clear();
534     m_textStyles = m_textStyles2;
535     m_textStrings = m_textStrings2;
536     m_addCompleteElement = m_addCompleteElement2;
537     m_textStyles2.clear();
538     m_textStrings2.clear();
539     m_addCompleteElement2.clear();
540 }
541 
applyParagraphProperties(const wvWare::ParagraphProperties & properties,KoGenStyle * style,const wvWare::Style * parentStyle,bool setDefaultAlign,Paragraph * paragraph,QChar * tabLeader,const QString & bgColor)542 void Paragraph::applyParagraphProperties(const wvWare::ParagraphProperties& properties,
543                                          KoGenStyle* style, const wvWare::Style* parentStyle,
544                                          bool setDefaultAlign, Paragraph *paragraph,
545                                          QChar* tabLeader, const QString& bgColor)
546 {
547     debugMsDoc;
548 
549     const wvWare::Word97::PAP* refPap;
550     if (parentStyle) {
551         refPap = &parentStyle->paragraphProperties().pap();
552     } else {
553         refPap = 0;
554     }
555 
556     if (!bgColor.isNull()) {
557         updateBgColor(bgColor);
558     }
559 
560     //pap is our paragraph properties object
561     const wvWare::Word97::PAP& pap = properties.pap();
562 
563     const KoGenStyle::PropertyType pt = KoGenStyle::ParagraphType;
564 
565     //paragraph alignment
566     //jc = justification code
567     if (!refPap || refPap->jc != pap.jc) {
568         if (pap.jc == 1)   //1 = center justify
569             style->addProperty("fo:text-align", "center", pt);
570         else if (pap.jc == 2)  //2 = right justify
571             style->addProperty("fo:text-align", "end", pt);
572         else if (pap.jc == 3)  //3 = left & right justify
573             style->addProperty("fo:text-align", "justify", pt);
574         else if (pap.jc == 4)  //4 = distributed .. fake it as justify
575             style->addProperty("fo:text-align", "justify", pt);
576         else //0 = left justify
577             style->addProperty("fo:text-align", "start", pt);
578     } else if (setDefaultAlign) {
579         // Set default align for page number field in header or footer
580         debugMsDoc << "setting default align for page number field in header or footer";
581         style->addProperty("fo:text-align", "center", pt);
582     }
583 
584     if (!refPap || refPap->fBiDi != pap.fBiDi) {
585         if (pap.fBiDi == 1)   //1 = right to left
586             style->addProperty("style:writing-mode", "rl-tb", pt);
587         else //0 = normal
588             style->addProperty("style:writing-mode", "lr-tb", pt);
589     }
590 
591     // If there is no parent style OR the parent and child background
592     // color don't match.
593     if ( !refPap ||
594          (refPap->shd.cvBack != pap.shd.cvBack) ||
595          (refPap->shd.isShdAuto() != pap.shd.isShdAuto()) ||
596          (refPap->shd.isShdNil() != pap.shd.isShdNil()) )
597     {
598         QString color = Conversion::shdToColorStr(pap.shd, currentBgColor(), QString());
599         if (!color.isNull()) {
600             updateBgColor(color);
601         } else {
602             color = "transparent";
603         }
604         style->addProperty("fo:background-color", color, pt);
605     }
606 
607     //dxaLeft1 = first-line indent from left margin (signed, relative to dxaLeft)
608     //dxaLeft = indent from left margin (signed)
609     //dxaRight = indent from right margin (signed)
610     if (!refPap || refPap->dxaLeft != pap.dxaLeft) {
611         // apply twip -> pt conversion, only if not in a list
612         if (pap.ilfo == 0) {
613             style->addPropertyPt("fo:margin-left", (double)pap.dxaLeft / 20.0, pt);
614         }
615     }
616     if (!refPap || refPap->dxaRight != pap.dxaRight) {
617         // apply twip -> pt conversion
618         style->addPropertyPt("fo:margin-right", (double)pap.dxaRight / 20.0, pt);
619     }
620     if (!refPap || refPap->dxaLeft1 != pap.dxaLeft1) {
621         // apply twip -> pt conversion, only if not in a list
622         if (pap.ilfo == 0) {
623             style->addPropertyPt("fo:text-indent", (double)pap.dxaLeft1 / 20.0, pt);
624         }
625     }
626 
627     //dyaBefore = vertical spacing before paragraph (unsigned)
628     //dyaAfter = vertical spacing after paragraph (unsigned)
629     if (!refPap || refPap->dyaBefore != pap.dyaBefore) {
630         double marginTop = (double)pap.dyaBefore / 20.0;
631         if (pap.dyaBeforeAuto) {
632            //TODO: Figure out the proper logic for automatic margins.
633             marginTop = 14;
634         }
635         style->addPropertyPt("fo:margin-top", marginTop, pt);
636     }
637     if (!refPap || refPap->dyaAfter != pap.dyaAfter) {
638         double marginBottom = (double)pap.dyaAfter / 20.0;
639         if (pap.dyaAfterAuto) {
640            //TODO: Figure out the proper logic for automatic margins.
641             marginBottom = 14;
642         }
643         style->addPropertyPt("fo:margin-bottom", marginBottom, pt);
644     }
645 
646     // Linespacing
647     //lspd = line spacing descriptor
648     //Conversion::lineSpacing() returns "0" (default), "oneandhalf," or "double"
649     //QString lineSpacingAttribute = Conversion::lineSpacing( pap.lspd );
650     if (!refPap || refPap->lspd.fMultLinespace != pap.lspd.fMultLinespace
651             || refPap->lspd.dyaLine != pap.lspd.dyaLine) {
652 
653         if (pap.lspd.fMultLinespace == 1) {
654             // Word will reserve for each line the (maximal height of
655             // the line*lspd.dyaLine)/240
656             //
657             // Get the proportion & turn it into a percentage for the
658             // attribute.
659             QString proportionalLineSpacing(QString::number(ceil(pap.lspd.dyaLine / 2.4f)));
660             style->addProperty("fo:line-height", proportionalLineSpacing.append("%"), pt);
661         } else if (pap.lspd.fMultLinespace == 0) {
662             // Magnitude of lspd.dyaLine specifies the amount of space
663             // that will be provided for lines in the paragraph in twips.
664             //
665             // See sprmPDyaLine in generator_wword8.htm
666             qreal value = qAbs((qreal)pap.lspd.dyaLine / (qreal)20.0); // twip -> pt
667             // lspd.dyaLine > 0 means "at least", < 0 means "exactly"
668             if (pap.lspd.dyaLine > 0)
669                 style->addPropertyPt("style:line-height-at-least", value, pt);
670             else if (pap.lspd.dyaLine < 0 && pap.dcs.fdct==0)
671                 style->addPropertyPt("fo:line-height", value, pt);
672         } else
673             warnMsDoc << "Unhandled LSPD::fMultLinespace value: "
674             << pap.lspd.fMultLinespace;
675     }
676 
677     //fKeep = keep entire paragraph on one page if possible
678     //fKeepFollow = keep paragraph on same page with next paragraph if possible
679     //fPageBreakBefore = start this paragraph on new page
680     if (!refPap || refPap->fKeep != pap.fKeep) {
681         if (pap.fKeep)
682             style->addProperty("fo:keep-together", "always", pt);
683         else
684             style->addProperty("fo:keep-together", "auto", pt);
685     }
686     if (!refPap || refPap->fKeepFollow != pap.fKeepFollow) {
687         if (pap.fKeepFollow)
688             style->addProperty("fo:keep-with-next", "always", pt);
689         else
690             style->addProperty("fo:keep-with-next", "auto", pt);
691     }
692     if (!refPap || refPap->fPageBreakBefore != pap.fPageBreakBefore) {
693         if (pap.fPageBreakBefore)
694             style->addProperty("fo:break-before", "page", pt);
695         else
696             style->addProperty("fo:break-before", "auto", pt);
697     }
698 
699     // Borders
700     //brcTop = specification for border above paragraph
701     //brcBottom = specification for border below paragraph
702     //brcLeft = specification for border to the left of paragraph
703     //brcRight = specification for border to the right of paragraph
704     //brcType: 0=none, 1=single, 2=thick, 3=double, 5=hairline, 6=dot,
705     //  7=dash large gap, 8=dot dash, 9=dot dot dash, 10=triple,
706     //  11=thin-thick small gap, ...
707     //
708     // TODO: Check if we can use fo:border instead of all the
709     //       fo:border-{top,bottom,left,right}
710     // TODO: Check if borderStyle is "double" and add attributes for that.
711     if (!refPap || refPap->brcTop.brcType != pap.brcTop.brcType) {
712         style->addProperty("fo:border-top", Conversion::setBorderAttributes(pap.brcTop), pt);
713     }
714     if (!refPap || refPap->brcBottom.brcType != pap.brcBottom.brcType) {
715         style->addProperty("fo:border-bottom", Conversion::setBorderAttributes(pap.brcBottom), pt);
716     }
717     if (!refPap || refPap->brcLeft.brcType != pap.brcLeft.brcType) {
718         style->addProperty("fo:border-left", Conversion::setBorderAttributes(pap.brcLeft), pt);
719     }
720     if (!refPap || refPap->brcRight.brcType != pap.brcRight.brcType) {
721         style->addProperty("fo:border-right", Conversion::setBorderAttributes(pap.brcRight), pt);
722     }
723 
724     // Padding
725     if (!refPap || refPap->brcTop.dptSpace != pap.brcTop.dptSpace) {
726         style->addPropertyPt("fo:padding-top", pap.brcTop.dptSpace, pt);
727     }
728     if (!refPap || refPap->brcBottom.dptSpace != pap.brcBottom.dptSpace) {
729         style->addPropertyPt("fo:padding-bottom", pap.brcBottom.dptSpace, pt);
730     }
731     if (!refPap || refPap->brcLeft.dptSpace != pap.brcLeft.dptSpace) {
732         style->addPropertyPt("fo:padding-left", pap.brcLeft.dptSpace, pt);
733     }
734     if (!refPap || refPap->brcRight.dptSpace != pap.brcRight.dptSpace) {
735         style->addPropertyPt("fo:padding-right", pap.brcRight.dptSpace, pt);
736     }
737 
738     // Drop Cap Style (DCS)
739     if (!refPap || refPap->dcs.fdct != pap.dcs.fdct || refPap->dcs.lines != pap.dcs.lines) {
740         if (paragraph) {
741             debugMsDoc << "Processing drop cap";
742             if (paragraph->m_textStrings.size() > 0)
743                 debugMsDoc << "String = """ << paragraph->m_textStrings[0] << """";
744             else
745                 debugMsDoc << "No drop cap string";
746 
747             paragraph->m_dropCapStatus = IsDropCapPara;
748             paragraph->m_dcs_fdct  = pap.dcs.fdct;
749             paragraph->m_dcs_lines = pap.dcs.lines;
750             paragraph->m_dropCapDistance = (qreal)pap.dxaFromText / (qreal)20.0;
751         }
752 #if 0
753         QBuffer buf;
754         buf.open(QIODevice::WriteOnly);
755         KoXmlWriter tmpWriter(&buf, 3);
756         tmpWriter.startElement("style:drop-cap");
757         tmpWriter.addAttribute("style:lines", pap.dcs.lines);
758         tmpWriter.addAttributePt("style:distance", (qreal)pap.dxaFromText / (qreal)20.0);
759         tmpWriter.addAttribute("style:length", pap.dcs.fdct > 0 ? 1 : 0);
760         tmpWriter.endElement();//style:drop-cap
761         buf.close();
762 
763         QString contents = QString::fromUtf8(buf.buffer(), buf.buffer().size());
764         style->addChildElement("style:drop-cap", contents);
765 #endif
766     }
767     //TODO: Compare with the parent style to avoid duplicity.
768 
769     // Tabulators, only if not in a list.  itbdMac = number of tabs stops
770     // defined for paragraph.  Must be in <0,64>.
771     if (pap.itbdMac) {
772 
773         QBuffer buf;
774         buf.open(QIODevice::WriteOnly);
775         KoXmlWriter tmpWriter(&buf, 3);//root, office:automatic-styles, style:style
776         tmpWriter.startElement("style:tab-stops");
777         for (int i = 0 ; i < pap.itbdMac ; ++i) {
778             tmpWriter.startElement("style:tab-stop");
779 
780             //rgdxaTab = array of { positions of itbdMac tab stops ; itbdMac
781             //tab descriptors } itbdMax == 64.
782             const wvWare::Word97::TabDescriptor &td = pap.rgdxaTab[i];
783             //td.dxaTab = position in twips
784             //QString pos( QString::number( (double)td.dxaTab / 20.0 ) );
785             tmpWriter.addAttributePt("style:position", (double)td.dxaTab / 20.0);
786 
787             //td.tbd.jc = justification code
788             switch (td.tbd.jc) {
789             case jcCenter:
790                 tmpWriter.addAttribute("style:type", "center");
791                 break;
792             case jcRight:
793                 tmpWriter.addAttribute("style:type", "right");
794                 break;
795             case jcDecimal:
796                 tmpWriter.addAttribute("style:type", "char");
797                 tmpWriter.addAttribute("style:char", ".");
798                 break;
799             case jcBar:
800                 //bar -> just creates a vertical bar at that point that's always visible
801                 warnMsDoc << "Unhandled tab justification code: " << td.tbd.jc;
802                 break;
803             default:
804                 //ODF: The default value for this attribute is left.
805                 break;
806             }
807             //td.tbd.tlc = tab leader code, default no leader (can be ignored)
808             QChar leader;
809             switch (td.tbd.tlc) {
810             case tlcDot:
811             case tlcMiddleDot:
812                 leader = QChar('.');
813                 break;
814             case tlcHyphen:
815                 leader = QChar('-');
816                 break;
817             case tlcUnderscore:
818             case tlcHeavy:
819                 leader = QChar('_');
820                 break;
821             case tlcNone:
822             default:
823                 //ODF: The default value for this attribute is “ “ (U+0020, SPACE).
824                 break;
825             }
826             //The value MUST be ignored if jc is equal jcBar.
827             if (td.tbd.jc != jcBar && leader.unicode() > 0) {
828                 tmpWriter.addAttribute("style:leader-text", leader);
829             }
830             tmpWriter.endElement();//style:tab-stop
831 
832             if (tabLeader) {
833                 *tabLeader = leader;
834             }
835         }
836         tmpWriter.endElement();//style:tab-stops
837         buf.close();
838         QString contents = QString::fromUtf8(buf.buffer(), buf.buffer().size());
839         //now write the tab info to the paragraph style
840         style->addChildElement("style:tab-stops", contents);
841     }
842 } //end applyParagraphProperties
843 
applyCharacterProperties(const wvWare::Word97::CHP * chp,KoGenStyle * style,const wvWare::Style * parentStyle,bool suppressFontSize,bool combineCharacters,const QString & bgColor)844 void Paragraph::applyCharacterProperties(const wvWare::Word97::CHP* chp, KoGenStyle* style, const wvWare::Style* parentStyle, bool suppressFontSize, bool combineCharacters, const QString& bgColor)
845 {
846     //TODO: Also compare against the CHPs of the paragraph style.  At the
847     //moment comparing against CHPs of the referred built-in character style.
848 
849     const KoGenStyle::PropertyType tt = KoGenStyle::TextType;
850 
851     //if we have a named style, set its CHP as the refChp
852     const wvWare::Word97::CHP* refChp;
853     if (parentStyle) {
854         refChp = &parentStyle->chp();
855     } else {
856         refChp = 0;
857     }
858 
859     //initialize the colors
860     if (!bgColor.isNull()) {
861         updateBgColor(bgColor);
862     }
863 
864     const quint8 bgColorsSize_bkp = m_bgColors.size();
865 
866     //fHighlight = when 1, characters are highlighted with color specified by
867     //chp.icoHighlight icoHighlight = highlight color (see chp.ico)
868     if (!refChp ||
869         (refChp->fHighlight != chp->fHighlight) ||
870         (refChp->icoHighlight != chp->icoHighlight))
871     {
872         QString color("transparent");
873         if (chp->fHighlight) {
874             color = Conversion::color(chp->icoHighlight, -1);
875             pushBgColor(color);
876         }
877         style->addProperty("fo:background-color", color, tt);
878     }
879 
880     if (!refChp ||
881         (refChp->shd.cvBack != chp->shd.cvBack) ||
882         (refChp->shd.isShdAuto() != chp->shd.isShdAuto()) ||
883         (refChp->shd.isShdNil() != chp->shd.isShdNil()))
884     {
885         QString color = Conversion::shdToColorStr(chp->shd, currentBgColor(), QString());
886         if (!color.isNull()) {
887             pushBgColor(color);
888         } else {
889             color = "transparent";
890         }
891         style->addProperty("fo:background-color", color, tt);
892     }
893 
894     //TODO: Check fo:background-color of the named character style
895 
896     //ico = color of text, but this has been replaced by cv
897     if (!refChp ||
898         (refChp->cv != chp->cv) ||
899         (chp->cv == wvWare::Word97::cvAuto))
900     {
901         QString color;
902         if (chp->cv == wvWare::Word97::cvAuto) {
903             //use the color context to set the proper font color
904             color = Conversion::computeAutoColor(chp->shd, currentBgColor(), QString());
905 
906             // NOTE: Have to specify fo:color explicitly because only
907             // basic support of style:use-window-font-color is
908             // implemented in calligra.  No support for text in shapes
909             // with solid fill.
910             //
911             // style->addProperty("style:use-window-font-color", "true", tt);
912         } else {
913             color = QString('#' + QString::number(chp->cv | 0xff000000, 16).right(6).toUpper());
914         }
915         style->addProperty("fo:color", color, tt);
916         // m_fontColor = color;
917     }
918 
919     //hps = font size in half points
920     if (!suppressFontSize && (!refChp || refChp->hps != chp->hps)) {
921         style->addPropertyPt(QString("fo:font-size"), ((qreal) chp->hps / 2), tt);
922     }
923 
924     //fBold = bold text if 1
925     if (!refChp || (refChp->fBold != chp->fBold)) {
926         style->addProperty(QString("fo:font-weight"), chp->fBold ? QString("bold") : QString("normal"), tt);
927     }
928 
929     //fItalic = italic text if 1
930     if (!refChp || refChp->fItalic != chp->fItalic)
931         style->addProperty(QString("fo:font-style"), chp->fItalic ? QString("italic") : QString("normal"), tt);
932 
933     // ********************
934     // style of underline
935     // ********************
936     if (!refChp || refChp->kul != chp->kul) {
937         // style:text-underline-color
938         QString color("font-color");
939         if (chp->cvUl != wvWare::Word97::cvAuto) {
940             color = QString('#' + QString::number(chp->cvUl | 0xff000000, 16).right(6).toUpper());
941         }
942         style->addProperty("style:text-underline-color", color, tt);
943         // style:text-underline-mode
944         style->addProperty("style:text-underline-mode", getTextUnderlineMode(chp->kul), tt);
945         // style:text-underline-style
946         QString ustyle(getTextUnderlineStyle(chp->kul));
947         if (!ustyle.isEmpty()) {
948             style->addProperty("style:text-underline-style", ustyle, tt);
949         }
950         // style:text-underline-type
951         style->addProperty("style:text-underline-type", getTextUnderlineType(chp->kul), tt);
952         // style:text-underline-width
953         style->addProperty("style:text-underline-width", getTextUnderlineWidth(chp->kul), tt);
954     }
955     //fstrike = use strikethrough if 1
956     //fDStrike = use double strikethrough if 1
957     if (!refChp || refChp->fStrike != chp->fStrike || refChp->fDStrike != chp->fDStrike) {
958         if (chp->fStrike)
959             style->addProperty("style:text-line-through-type", "single", tt);
960         else if (chp->fDStrike)
961             style->addProperty("style:text-line-through-type", "double", tt);
962         else
963             style->addProperty("style:text-line-through-type", "none", tt);
964     }
965 
966     //font attribute (uppercase, lowercase (not in MSWord), small caps)
967     //fCaps = displayed with all caps when 1, no caps when 0
968     //fSmallCaps = displayed with small caps when 1, no small caps when 0
969     if (!refChp || refChp->fCaps != chp->fCaps || refChp->fSmallCaps != chp->fSmallCaps) {
970         if (chp->fCaps)
971             style->addProperty("fo:text-transform", "uppercase", tt);
972         if (chp->fSmallCaps)
973             style->addProperty("fo:font-variant", "small-caps", tt);
974     }
975 
976     //iss = superscript/subscript indices
977     if (!refChp || refChp->iss != chp->iss) {
978         if (chp->iss == 1)   //superscript
979             style->addProperty("style:text-position", "super", tt);
980         else if (chp->iss == 2)   //subscript
981             style->addProperty("style:text-position", "sub", tt);
982         else   //no superscript or subscript
983             style->addProperty("style:text-position", "0% 100%", tt);
984     }
985 
986     //fShadow = text has shadow if 1
987     //fImprint = text engraved if 1
988     if (!refChp || refChp->fShadow != chp->fShadow || refChp->fImprint != chp->fImprint) {
989         if (chp->fShadow)
990             style->addProperty("fo:text-shadow", "1pt", tt);
991         if (chp->fImprint)
992             style->addProperty("style:font-relief", "engraved", tt);
993     }
994 
995     //fOutline = text is outline if 1
996     if (!refChp || refChp->fOutline != chp->fOutline) {
997         if (chp->fOutline)
998             style->addProperty("style:text-outline", "true", tt);
999         else
1000             style->addProperty("style:text-outline", "false", tt);
1001     }
1002 
1003     // if the characters are combined, add proper style
1004     if (combineCharacters) {
1005         style->addProperty("style:text-combine", "letters", tt);
1006     }
1007 
1008     //dxaSpace = letterspacing in twips
1009     if (!refChp || refChp->dxaSpace != chp->dxaSpace) {
1010         double value =  chp->dxaSpace / 20.0; // twips -> pt
1011         style->addPropertyPt("fo:letter-spacing", value, tt);
1012     }
1013     //pctCharwidth = pct stretch doesn't seem to have an ODF ccounterpart but Qt could support it
1014 
1015     //fTNY = 1 when text is vertical
1016     if (!refChp || refChp->fTNY != chp->fTNY) {
1017         if (chp->fTNY) {
1018             style->addProperty("style:text-rotation-angle", 90, tt);
1019             if (chp->fTNYCompress) {
1020                 style->addProperty("style:text-rotation-scale", "fixed", tt);
1021             } else {
1022                 style->addProperty("style:text-rotation-scale", "line-height", tt);
1023             }
1024         }
1025     }
1026 
1027     //wCharScale - MUST be greater than or equal to 1 and less than or equal to 600
1028     if (!refChp || refChp->wCharScale != chp->wCharScale) {
1029         if (chp->wCharScale) {
1030             style->addProperty("style:text-scale", QString::number(chp->wCharScale) + "%", tt);
1031         }
1032     }
1033 
1034     //remove the background-colors collected for this text chunk
1035     while (m_bgColors.size() > bgColorsSize_bkp) {
1036         popBgColor();
1037     }
1038 }
1039 
dropCapStatus() const1040 Paragraph::DropCapStatus Paragraph::dropCapStatus() const
1041 {
1042     return m_dropCapStatus;
1043 }
1044 
getDropCapData(QString * string,int * type,int * lines,qreal * distance,QString * style) const1045 void Paragraph::getDropCapData(QString *string, int *type, int *lines, qreal *distance, QString *style) const
1046 {
1047     // As far as I can see there is only ever a single character as drop cap.
1048     *string = m_textStrings.isEmpty() ? QString() : m_textStrings[0];
1049     *type = m_dcs_fdct;
1050     *lines = m_dcs_lines;
1051     *distance = m_dropCapDistance;
1052     *style = m_dropCapStyleName;
1053 }
1054 
1055 
addDropCap(QString & string,int type,int lines,qreal distance,const QString & style)1056 void Paragraph::addDropCap(QString &string, int type, int lines, qreal distance, const QString &style)
1057 {
1058     debugMsDoc << "combining drop cap paragraph: " << string;
1059     if (m_dropCapStatus == IsDropCapPara)
1060         debugMsDoc << "This paragraph already has a dropcap set!";
1061 
1062     m_dropCapStatus = HasDropCapIntegrated;
1063 
1064     // Get the drop cap data.
1065     m_dcs_fdct        = type;
1066     m_dcs_lines       = lines;
1067     m_dropCapDistance = distance;
1068     m_dropCapStyleName = style;
1069 
1070     // Add the actual text.
1071     // Here we assume that there will only be one text snippet for the drop cap.
1072 #if 1
1073 
1074 #if 1
1075     debugMsDoc << "size: " << m_textStrings.size();
1076     if (m_textStrings.isEmpty()) {
1077         m_textStrings.append(string);
1078         KoGenStyle* style = 0;
1079         m_textStyles.insert(m_textStyles.begin(), style);
1080     } else {
1081         m_textStrings[0].prepend(string);
1082     }
1083 #else
1084     m_textStrings.prepend(string);
1085     KoGenStyle* style = 0;
1086     m_textStyles.insert(m_textStyles.begin(), style);
1087 #endif
1088 
1089 #else
1090     std::vector<QString>            tempStrings;
1091     std::vector<const KoGenStyle*>  tempStyles;
1092     tempStrings.push_back(dropCapParagraph->m_textStrings[0]);
1093     KoGenStyle* style = 0;
1094     tempStyles.push_back(style);
1095 
1096     if (dropCapParagraph->m_textStrings.size() > 0) {
1097         for (uint i = 0; i < m_textStrings.size(); ++i) {
1098             tempStrings.push_back(m_textStrings.[i]);
1099             tempStyles.push_back(m_textStyles.[i]);
1100         }
1101     }
1102 
1103     m_textStrings = tempStrings;
1104     m_textStyles  = tempStyles;
1105 #endif
1106 }
1107 
1108 
strings() const1109 int Paragraph::strings() const
1110 {
1111     return m_textStrings.size();
1112 }
1113 
string(int index) const1114 QString Paragraph::string(int index) const
1115 {
1116     return m_textStrings[index];
1117 }
1118 
createTextStyle(wvWare::SharedPtr<const wvWare::Word97::CHP> chp,const wvWare::StyleSheet & styles)1119 QString Paragraph::createTextStyle(wvWare::SharedPtr<const wvWare::Word97::CHP> chp, const wvWare::StyleSheet& styles)
1120 {
1121     if (!chp) {
1122         return QString();
1123     }
1124     const wvWare::Style* msTextStyle = styles.styleByIndex(chp->istd);
1125     if (!msTextStyle && styles.size()) {
1126         msTextStyle = styles.styleByID(stiNormalChar);
1127         debugMsDoc << "Invalid reference to text style, reusing NormalChar";
1128     }
1129     Q_ASSERT(msTextStyle);
1130 
1131     QString msTextStyleName = Conversion::styleName2QString(msTextStyle->name());
1132     debugMsDoc << "text based on characterstyle " << msTextStyleName;
1133 
1134     bool suppresFontSize = false;
1135     if (m_paragraphProperties->pap().dcs.lines > 1) {
1136         suppresFontSize = true;
1137     }
1138     KoGenStyle textStyle(KoGenStyle::TextAutoStyle, "text");
1139     if (m_inStylesDotXml) {
1140         textStyle.setAutoStyleInStylesDotXml(true);
1141     }
1142 
1143     applyCharacterProperties(chp, &textStyle, msTextStyle, suppresFontSize, m_combinedCharacters);
1144 
1145     QString textStyleName('T');
1146     textStyleName = m_mainStyles->insert(textStyle, textStyleName);
1147     return textStyleName;
1148 }
1149 
getStrokeValue(const uint brcType)1150 const char* getStrokeValue(const uint brcType)
1151 {
1152     //TODO: create corresponding dash styles
1153     switch (brcType) {
1154     case 0x01: //A single line.
1155     case 0x03: //A double line.
1156     case 0x05: //A thin single solid line.
1157     case 0x14: //A single wavy line.
1158     case 0x15: //A double wavy line.
1159     case 0x18: //threeDEmboss
1160     case 0x19: //threeDEngrave
1161     case 0x1A: //outset
1162     case 0x1B: //inset
1163     return "solid";
1164     default:
1165         return "none";
1166     }
1167 }
1168 
getTextUnderlineMode(const uint kul)1169 const char* getTextUnderlineMode(const uint kul)
1170 {
1171     switch (kul) {
1172     case kulWords:
1173         return "skip-white-space";
1174     default:
1175         return "continuous";
1176     }
1177 }
1178 
getTextUnderlineStyle(const uint kul)1179 const char* getTextUnderlineStyle(const uint kul)
1180 {
1181     // The values are none, solid, dotted, dash, long-dash, dot-dash,
1182     // dot-dot-dash or wave.  The defined value for the
1183     // style:text-underline-style attribute is none: text has no underlining.
1184     switch (kul) {
1185     case kulSingle:
1186     case kulWords:
1187     case kulDouble:
1188         return "solid";
1189     case kulDotted:
1190     case kulDottedHeavy:
1191         return "dotted";
1192     case kulThick:
1193     return "solid";
1194     case kulDash:
1195     case kulDashHeavy:
1196         return "dash";
1197     case kulDashLong:
1198     case kulDashLongHeavy:
1199         return "long-dash";
1200     case kulDotDash:
1201     case kulDotDashHeavy:
1202         return "dot-dash";
1203     case kulDotDotDash:
1204     case kulDotDotDashHeavy:
1205         return "dot-dot-dash";
1206     case kulWavy:
1207     case kulWavyDouble:
1208     case kulWavyHeavy:
1209         return "wave";
1210     case 5: // hidden - This makes no sense as an underline property!
1211     case 8:
1212         //NOTE: Styles of underline not specified in [MS-DOC] - v20101219
1213         debugMsDoc << "Unknown style of underline detected!";
1214         return "";
1215     case kulNone:
1216     default:
1217         return "";
1218     };
1219 }
1220 
getTextUnderlineType(const uint kul)1221 const char* getTextUnderlineType(const uint kul)
1222 {
1223     //The values are none, single or double.
1224     switch (kul) {
1225     case kulNone:
1226         return "none";
1227     case kulDouble:
1228     case kulWavyDouble:
1229         return "double";
1230     default:
1231         return "single";
1232     }
1233 }
1234 
getTextUnderlineWidth(const uint kul)1235 const char* getTextUnderlineWidth(const uint kul)
1236 {
1237     // The values are auto, normal, bold, thin, medium, thick, a value of type
1238     // positiveInteger, a value of type percent or a value of type
1239     // positiveLength.
1240     switch (kul) {
1241     case kulThick:
1242         return "thick";
1243     case kulDottedHeavy:
1244     case kulDashHeavy:
1245     case kulDashLongHeavy:
1246     case kulDotDashHeavy:
1247     case kulDotDotDashHeavy:
1248     case kulWavyHeavy:
1249         return "bold";
1250     default:
1251         return "auto";
1252     }
1253 }
1254