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