File indexing completed on 2025-01-12 10:34:20

0001 /* This file is part of the KDE project
0002    SPDX-FileCopyrightText: 2010 KO GmbH <jos.van.den.oever@kogmbh.com>
0003    SPDX-FileCopyrightText: 2011 Lukáš Tvrdý <lukas.tvrdy@ixonos.com>
0004 
0005    SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "ODrawToOdf.h"
0009 
0010 #include "drawstyle.h"
0011 #include "msodraw.h"
0012 #include <KoGenStyles.h>
0013 #include <KoXmlWriter.h>
0014 #include <QColor>
0015 #include <QBuffer>
0016 #include "generated/leinputstream.h"
0017 
0018 #include <cmath>
0019 
0020 //! Converts EMU (English Metric Unit) value (integer or double) to points
0021 #define EMU_TO_POINT(emu) ((emu)/12700.0)
0022 
0023 using namespace MSO;
0024 
0025 /**
0026  * Return the bounding rectangle for this object.
0027  **/
0028 QRectF ODrawToOdf::getRect(const OfficeArtFSPGR &r)
0029 {
0030     return QRect(r.xLeft, r.yTop, r.xRight - r.xLeft, r.yBottom - r.yTop);
0031 }
0032 
0033 void ODrawToOdf::processGroupShape(const MSO::OfficeArtSpgrContainer& o, Writer& out)
0034 {
0035     if (o.rgfb.size() < 2) return;
0036 
0037     //The first container MUST be an OfficeArtSpContainer record, which
0038     //MUST contain shape information for the group.  MS-ODRAW, 2.2.16
0039     const OfficeArtSpContainer* sp = o.rgfb[0].anon.get<OfficeArtSpContainer>();
0040 
0041     //An OfficeArtFSPGR record specifies the coordinate system of the group
0042     //shape.  The anchors of the child shape are expressed in this coordinate
0043     //system.  This record’s container MUST be a group shape.
0044     if (sp && sp->shapeProp.fGroup) {
0045         QRectF oldCoords;
0046         if (!sp->shapeProp.fPatriarch) {
0047             out.xml.startElement("draw:g");
0048 
0049             //TODO: rotation and flipping of group shapes
0050             const DrawStyle ds(0, 0, sp);
0051             qreal rotation = toQReal(ds.rotation());
0052             out.g_rotation += rotation;
0053             out.g_flipH = sp->shapeProp.fFlipH;
0054             out.g_flipV = sp->shapeProp.fFlipV;
0055 
0056             if (sp->clientAnchor && sp->shapeGroup) {
0057                 oldCoords = client->getRect(*sp->clientAnchor);
0058             }
0059         }
0060         if (oldCoords.isValid()) {
0061             Writer out_trans = out.transform(oldCoords, getRect(*sp->shapeGroup));
0062             for (int i = 1; i < o.rgfb.size(); ++i) {
0063                 processDrawing(o.rgfb[i], out_trans);
0064             }
0065         } else {
0066             for (int i = 1; i < o.rgfb.size(); ++i) {
0067                 processDrawing(o.rgfb[i], out);
0068             }
0069         }
0070         if (!sp->shapeProp.fPatriarch) {
0071             out.xml.endElement(); //draw:g
0072         }
0073     }
0074 }
0075 
0076 void ODrawToOdf::processDrawing(const OfficeArtSpgrContainerFileBlock& of,
0077                                 Writer& out)
0078 {
0079     if (of.anon.is<OfficeArtSpgrContainer>()) {
0080         processGroupShape(*of.anon.get<OfficeArtSpgrContainer>(), out);
0081     } else {
0082         processDrawingObject(*of.anon.get<OfficeArtSpContainer>(), out);
0083     }
0084 }
0085 void ODrawToOdf::addGraphicStyleToDrawElement(Writer& out,
0086         const OfficeArtSpContainer& o)
0087 {
0088     KoGenStyle style;
0089     const OfficeArtDggContainer* drawingGroup = 0;
0090     const OfficeArtSpContainer* master = 0;
0091 
0092     if (client) {
0093         drawingGroup = client->getOfficeArtDggContainer();
0094 
0095         //locate the OfficeArtSpContainer of the master shape
0096         if (o.shapeProp.fHaveMaster) {
0097             const DrawStyle tmp(0, &o);
0098             quint32 spid = tmp.hspMaster();
0099             master = client->getMasterShapeContainer(spid);
0100         }
0101     }
0102     const DrawStyle ds(drawingGroup, master, &o);
0103     if (client) {
0104         style = client->createGraphicStyle(o.clientTextbox.data(),
0105                                            o.clientData.data(), ds, out);
0106     }
0107     defineGraphicProperties(style, ds, out.styles);
0108 
0109     if (client) {
0110         client->addTextStyles(o.clientTextbox.data(),
0111                               o.clientData.data(), style, out);
0112     }
0113 }
0114 
0115 namespace
0116 {
0117     QString format(double v)
0118     {
0119         static const QString f("%1");
0120         static const QString e("");
0121         static const QRegExp r("\\.?0+$");
0122         return f.arg(v, 0, 'f').replace(r, e);
0123     }
0124 
0125     QString pt(double v)
0126     {
0127         static const QString pt("pt");
0128         return format(v) + pt;
0129     }
0130 
0131     QString percent(double v)
0132     {
0133         return format(v) + '%';
0134     }
0135 } //namespace
0136 
0137 void ODrawToOdf::defineGraphicProperties(KoGenStyle& style, const DrawStyle& ds, KoGenStyles& styles)
0138 {
0139     const KoGenStyle::PropertyType gt = KoGenStyle::GraphicType;
0140     // dr3d:ambient-color
0141     // dr3d:back-scale
0142     // dr3d:backface-culling
0143     // dr3d:close-back
0144     // dr3d:close-front
0145     // dr3d:depth
0146     // dr3d:diffuse-color
0147     // dr3d:edge-rounding
0148     // dr3d:edge-rounding-mode
0149     // dr3d:emissive-color
0150     // dr3d:end-angle
0151     // dr3d:horizontal-segments
0152     // dr3d:lighting-mode
0153     // dr3d:normals-direction
0154     // dr3d:normals-kind
0155     // dr3d:shadow
0156     // dr3d:shininess
0157     // dr3d:specular-color
0158     // dr3d:texture-filter
0159     // dr3d:texture-generation-mode-x
0160     // dr3d:texture-generation-mode-y
0161     // dr3d:texture-kind
0162     // dr3d:texture-mode
0163     // dr3d:vertical-segments
0164     // draw:auto-grow-height
0165     style.addProperty("draw:auto-grow-height", ds.fFitShapeToText(), gt);
0166     // draw:auto-grow-width
0167     style.addProperty("draw:auto-grow-width", ds.fFitShapeToText() && ds.wrapText()==msowrapNone, gt);
0168     // draw:blue
0169     // draw:caption-angle
0170     // draw:caption-angle-type
0171     // draw:caption-escape
0172     // draw:caption-escape-direction
0173     // draw:caption-fit-line-length
0174     // draw:caption-gap
0175     // draw:caption-line-length
0176     // draw:caption-type
0177     // draw:color-inversion
0178     // draw:color-mode
0179     if (ds.fPictureBiLevel()) {
0180         style.addProperty("draw:color-mode", "mono", gt);
0181     } else if (ds.fPictureGray()) {
0182         style.addProperty("draw:color-mode", "greyscale", gt);
0183     }
0184     // draw:contrast
0185     // draw:decimal-places
0186     // draw:end-guide
0187     // draw:end-line-spacing-horizontal
0188     // draw:end-line-spacing-vertical
0189 
0190     // NOTE: fFilled specifies whether fill of the shape is render based on the
0191     // properties of the "fill style" property set.
0192     if (ds.fFilled()) {
0193         // draw:fill ("bitmap", "gradient", "hatch", "none" or "solid")
0194         qint32 fillType = ds.fillType();
0195         style.addProperty("draw:fill", getFillType(fillType), gt);
0196         // draw:fill-color
0197         // only set the color if the fill type is 'solid' because OOo ignores
0198         // fill='none' if the color is set
0199         switch (fillType) {
0200         case msofillSolid:
0201         {
0202             if (!client) break;
0203             QColor color = processOfficeArtCOLORREF(ds.fillColor(), ds);
0204             style.addProperty("draw:fill-color", color.name(), gt);
0205             break;
0206         }
0207         // draw:fill-gradient-name
0208         case msofillShade:
0209         case msofillShadeCenter:
0210         case msofillShadeShape:
0211         case msofillShadeScale:
0212         case msofillShadeTitle:
0213         {
0214             if (!client) break;
0215             KoGenStyle gs(KoGenStyle::LinearGradientStyle);
0216             defineGradientStyle(gs, ds);
0217             QString gname = styles.insert(gs);
0218             style.addProperty("draw:fill-gradient-name", gname, gt);
0219             break;
0220         }
0221         // draw:fill-hatch-name
0222         // draw:fill-hatch-solid
0223         // draw:fill-image-height
0224         // draw:fill-image-name
0225         case msofillPattern:
0226         case msofillTexture:
0227         case msofillPicture:
0228         {
0229             if (!client) break;
0230             quint32 fillBlip = ds.fillBlip();
0231             QString fillImagePath;
0232             fillImagePath = client->getPicturePath(fillBlip);
0233             if (!fillImagePath.isEmpty()) {
0234                 style.addProperty("draw:fill-image-name",
0235                                   "fillImage" + QString::number(fillBlip), gt);
0236 
0237                 style.addProperty("style:repeat", getRepeatStyle(fillType), gt);
0238             }
0239             break;
0240         }
0241         case msofillBackground:
0242         default:
0243             break;
0244         }
0245         // draw:fill-image-ref-point
0246         // draw:fill-image-ref-point-x
0247         // draw:fill-image-ref-point-y
0248         // draw:fill-image-width
0249         // draw:opacity
0250         style.addProperty("draw:opacity",
0251                           percent(100.0 * toQReal(ds.fillOpacity())), gt);
0252         // draw:opacity-name
0253     } else {
0254         style.addProperty("draw:fill", "none", gt);
0255     }
0256     // draw:fit-to-contour
0257     // draw:fit-to-size
0258     // draw:frame-display-border
0259     // draw:frame-display-scrollbar
0260     // draw:frame-margin-horizontal
0261     // draw:frame-margin-vertical
0262     // draw:gamma
0263     // draw:gradient-step-count
0264     // draw:green
0265     // draw:guide-distance
0266     // draw:guide-overhang
0267     // draw:image-opacity
0268     // draw:line-distance
0269     // draw:luminance
0270 
0271     // TODO: improve marker width calculation
0272     qreal lineWidthPt = EMU_TO_POINT(ds.lineWidth());
0273     // markers are shape specific and thus do NOT belong into the
0274     // default graphic style
0275     if (ds.fLine() && ( ds.shapeType() != msosptNil )) {
0276         quint32 lineEndArrowhead = ds.lineEndArrowhead();
0277         if (lineEndArrowhead > 0 && lineEndArrowhead < 6) {
0278             // draw:marker-end
0279             style.addProperty("draw:marker-end", defineMarkerStyle(styles, lineEndArrowhead), gt);
0280             // draw:marker-end-center
0281             style.addProperty("draw:marker-end-center", "false", gt);
0282             // draw:marker-end-width
0283             style.addPropertyPt("draw:marker-end-width",
0284                                 lineWidthPt * 2 + ds.lineEndArrowWidth(), gt);
0285         }
0286         quint32 lineStartArrowhead = ds.lineStartArrowhead();
0287         if (lineStartArrowhead > 0 && lineStartArrowhead < 6) {
0288             // draw:marker-start
0289             style.addProperty("draw:marker-start", defineMarkerStyle(styles, lineStartArrowhead), gt);
0290             // draw:marker-start-center
0291             style.addProperty("draw:marker-start-center", "false", gt);
0292             // draw:marker-start-width
0293             style.addPropertyPt("draw:marker-start-width",
0294                                 lineWidthPt * 2 + ds.lineStartArrowWidth(), gt);
0295         }
0296     }
0297     // draw:measure-align
0298     // draw:measure-vertical-align
0299     // draw:ole-draw-aspect
0300     // draw:parallel
0301     // draw:placing
0302     // draw:red
0303     // draw:secondary-fill-color
0304     if (ds.fShadow()) {
0305         // draw:shadow
0306         style.addProperty("draw:shadow", "visible", gt);
0307         // draw:shadow-color
0308         if (client) {
0309             QColor clr = processOfficeArtCOLORREF(ds.shadowColor(), ds);
0310             style.addProperty("draw:shadow-color", clr.name(), gt);
0311         }
0312         // NOTE: shadowOffset* properties MUST exist if shadowType
0313         // property equals msoshadowOffset or msoshadowDouble,
0314         // otherwise MUST be ignored, MS-ODRAW 2.3.13.6
0315         quint32 type = ds.shadowType();
0316         if ((type == 0) || (type == 1)) {
0317             // draw:shadow-offset-x
0318             style.addPropertyPt("draw:shadow-offset-x", EMU_TO_POINT(ds.shadowOffsetX()), gt);
0319             // draw:shadow-offset-y
0320             style.addPropertyPt("draw:shadow-offset-y", EMU_TO_POINT(ds.shadowOffsetY()), gt);
0321         }
0322         // draw:shadow-opacity
0323         float shadowOpacity = toQReal(ds.shadowOpacity());
0324         style.addProperty("draw:shadow-opacity", percent(100*shadowOpacity), gt);
0325     } else {
0326         style.addProperty("draw:shadow", "hidden", gt);
0327     }
0328     // draw:show-unit
0329     // draw:start-guide
0330     // draw:start-line-spacing-horizontal
0331     // draw:start-line-spacing-vertical
0332     // draw:stroke ('dash', 'none' or 'solid')
0333     if (ds.fLine()) {
0334         quint32 lineDashing = ds.lineDashing();
0335 
0336         // NOTE: OOo interprets solid line of width 0 as hairline, so
0337         // stroke *must* be "none" in order to be NOT display in OOo.
0338         if (lineWidthPt == 0) {
0339             style.addProperty("draw:stroke", "none", gt);
0340         } else if (lineDashing > 0 && lineDashing < 11) {
0341             // NOTE: A "dash" looks wrong in Calligra/LO when
0342             // svg:stroke-linecap is applied.
0343             style.addProperty("draw:stroke", "dash", gt);
0344             style.addProperty("draw:stroke-dash", defineDashStyle(styles, lineDashing), gt);
0345         } else {
0346             style.addProperty("draw:stroke", "solid", gt);
0347             // svg:stroke-linecap
0348             style.addProperty("svg:stroke-linecap", getStrokeLineCap(ds.lineEndCapStyle()), gt);
0349         }
0350         if (lineWidthPt != 0) {
0351             // draw:stroke-linejoin
0352             style.addProperty("draw:stroke-linejoin", getStrokeLineJoin(ds.lineJoinStyle()), gt);
0353             // svg:stroke-color
0354             if (client) {
0355                 QColor clr = processOfficeArtCOLORREF(ds.lineColor(), ds);
0356                 style.addProperty("svg:stroke-color", clr.name(), gt);
0357             }
0358             // svg:stroke-opacity
0359             style.addProperty("svg:stroke-opacity", percent(100.0 * ds.lineOpacity() / 0x10000), gt);
0360             // svg:stroke-width
0361             style.addProperty("svg:stroke-width", pt(lineWidthPt), gt);
0362         }
0363     }
0364     // NOTE: Seems to be only relevant for master shapes. A usecase
0365     // would be a user editing a master slide.
0366     // else if (ds.fNoLineDrawDash()) {
0367     //     style.addProperty("draw:stroke", "dash", gt);
0368     //     style.addProperty("draw:stroke-dash", defineDashStyle(styles, msolineDashSys), gt);
0369     // }
0370     else {
0371         style.addProperty("draw:stroke", "none", gt);
0372     }
0373     // draw:stroke-dash-names
0374     // draw:symbol-color
0375     // draw:textarea-horizontal-align
0376     style.addProperty("draw:textarea-horizontal-align", getHorizontalAlign(ds.anchorText()), gt);
0377     // draw:textarea-vertical-align
0378     style.addProperty("draw:textarea-vertical-align", getVerticalAlign(ds.anchorText()), gt);
0379     // draw:tile-repeat-offset
0380     // draw:unit
0381     // draw:visible-area-height
0382     // draw:visible-area-left
0383     // draw:visible-area-top
0384     // draw:visible-area-width
0385     // draw:wrap-influence-on-position
0386     // fo:background-color
0387     // fo:border
0388     // fo:border-bottom
0389     // fo:border-left
0390     // fo:border-right
0391     // fo:border-top
0392     // fo:clip
0393     // fo:margin
0394     // fo:margin-bottom
0395     // fo:margin-left
0396     // fo:margin-right
0397     // fo:margin-top
0398     style.addPropertyPt("fo:margin-bottom", EMU_TO_POINT(ds.dyWrapDistBottom()), gt);
0399     style.addPropertyPt("fo:margin-left", EMU_TO_POINT(ds.dxWrapDistLeft()), gt);
0400     style.addPropertyPt("fo:margin-right", EMU_TO_POINT(ds.dxWrapDistRight()), gt);
0401     style.addPropertyPt("fo:margin-top", EMU_TO_POINT(ds.dyWrapDistTop()), gt);
0402     // fo:max-height
0403     // fo:max-width
0404     // fo:min-height
0405     // fo:min-width
0406     // fo:padding
0407     // fo:padding-left
0408     // fo:padding-top
0409     // fo:padding-right
0410     // fo:padding-bottom
0411     if (!ds.fAutoTextMargin()) {
0412         style.addPropertyPt("fo:padding-left", EMU_TO_POINT(ds.dxTextLeft()), gt);
0413         style.addPropertyPt("fo:padding-top", EMU_TO_POINT(ds.dyTextTop()), gt);
0414         style.addPropertyPt("fo:padding-right", EMU_TO_POINT(ds.dxTextRight()), gt);
0415         style.addPropertyPt("fo:padding-bottom", EMU_TO_POINT(ds.dyTextBottom()), gt);
0416     } else {
0417         // default internal margins for text on shapes
0418         style.addPropertyPt("fo:padding-left", EMU_TO_POINT(0x00016530), gt);
0419         style.addPropertyPt("fo:padding-top", EMU_TO_POINT(0x0000B298), gt);
0420         style.addPropertyPt("fo:padding-right", EMU_TO_POINT(0x00016530), gt);
0421         style.addPropertyPt("fo:padding-bottom", EMU_TO_POINT(0x0000B298), gt);
0422     }
0423     // fo:wrap-option
0424     // style:background-transparency
0425     // style:border-line-width
0426     // style:border-line-width-bottom
0427     // style:border-line-width-left
0428     // style:border-line-width-right
0429     // style:border-line-width-top
0430     // style:editable
0431     // style:flow-with-text
0432     style.addProperty("style:flow-with-text", ds.fLayoutInCell(), gt);
0433     // style:horizontal-pos
0434     // NOTE: tests on PPT, XLS required
0435 //     style.addProperty("style:horizontal-pos", getHorizontalPos(ds.posH()), gt);
0436     // style:horizontal-rel
0437     // NOTE: tests on PPT, XLS required
0438 //     style.addProperty("style:horizontal-rel", getHorizontalRel(ds.posRelH()), gt);
0439     // style:mirror
0440     // style:number-wrapped-paragraphs
0441     // style:overflow-behavior
0442     // style:print-content
0443     // style:protect
0444     // style:rel-height
0445     // style:rel-width
0446     // style:repeat // handled for image see draw:fill-image-name
0447     // style:run-through
0448     // style:shadow
0449     // style:shrink-to-fit
0450     // style:vertical-pos
0451     // NOTE: tests on PPT, XLS required
0452 //     style.addProperty("style:vertical-pos", getVerticalPos(ds.posV()), gt);
0453     // style:vertical-rel
0454     // NOTE: tests on PPT, XLS required
0455 //     style.addProperty("style:vertical-rel", getVerticalRel(ds.posRelV()), gt);
0456     // style:wrap
0457     // style:wrap-contour
0458     // style:wrap-contour-mode
0459     // style:wrap-dynamic-threshold
0460     // style:writing-mode
0461     // svg:fill-rule
0462     QString fillRule(getFillRule(ds.shapeType()));
0463     if (!fillRule.isEmpty()) {
0464         style.addProperty("svg:fill-rule" ,fillRule, gt);
0465     }
0466     // svg:height
0467     // svg:width
0468     // svg:x
0469     // svg:y
0470     // text:anchor-page-number
0471     // text:anchor-type
0472     // text:animation
0473     // text:animation-delay
0474     // text:animation-direction
0475     // text:animation-repeat
0476     // text:animation-start-inside
0477     // text:animation-steps
0478     // text:animation-stop-inside
0479 }
0480 
0481 namespace
0482 {
0483     const char* const markerStyles[6] = {
0484         "", "msArrowEnd_20_5", "msArrowStealthEnd_20_5", "msArrowDiamondEnd_20_5",
0485         "msArrowOvalEnd_20_5", "msArrowOpenEnd_20_5"
0486     };
0487 }
0488 
0489 QString ODrawToOdf::defineMarkerStyle(KoGenStyles& styles, const quint32 arrowType)
0490 {
0491     if ( !(arrowType > msolineNoEnd && arrowType < msolineArrowChevronEnd) ) {
0492         return QString();
0493     }
0494 
0495     const QString name(markerStyles[arrowType]);
0496 
0497     if (styles.style(name, "")) {
0498         return name;
0499     }
0500 
0501     KoGenStyle marker(KoGenStyle::MarkerStyle);
0502     marker.addAttribute("draw:display-name",  QString(markerStyles[arrowType]).replace("_20_", " "));
0503 
0504     // sync with LO
0505     switch (arrowType) {
0506     case msolineArrowStealthEnd:
0507         marker.addAttribute("svg:viewBox", "0 0 318 318");
0508         marker.addAttribute("svg:d", "m159 0 159 318-159-127-159 127z");
0509         break;
0510     case msolineArrowDiamondEnd:
0511         marker.addAttribute("svg:viewBox", "0 0 318 318");
0512         marker.addAttribute("svg:d", "m159 0 159 159-159 159-159-159z");
0513         break;
0514     case msolineArrowOvalEnd:
0515         marker.addAttribute("svg:viewBox", "0 0 318 318");
0516         marker.addAttribute("svg:d", "m318 0c0-87-72-159-159-159s-159 72-159 159 72 159 159 159 159-72 159-159z");
0517         break;
0518     case msolineArrowOpenEnd:
0519         marker.addAttribute("svg:viewBox", "0 0 477 477");
0520         marker.addAttribute("svg:d", "m239 0 238 434-72 43-166-305-167 305-72-43z");
0521         break;
0522     case msolineArrowEnd:
0523     default:
0524         marker.addAttribute("svg:viewBox", "0 0 318 318");
0525         marker.addAttribute("svg:d", "m159 0 159 318h-318z");
0526         break;
0527     }
0528     return styles.insert(marker, name, KoGenStyles::DontAddNumberToName);
0529 }
0530 
0531 void ODrawToOdf::defineGradientStyle(KoGenStyle& style, const DrawStyle& ds)
0532 {
0533     // TODO: another fill types
0534 
0535     // convert angle to two points representing crossing of
0536     // the line with rectangle to use it in svg
0537     // size of rectangle is 100*100 with the middle in 0,0
0538     // line coordinates are x1,y1; 0,0; x2,y2
0539     int dx=0,dy=0;
0540     int angle = (int)toQReal(ds.fillAngle());
0541 
0542     // from observations of the documents it seems
0543     // that angle is stored in -180,180 in MS 2003 documents
0544     if (angle < 0) {
0545         angle = angle + 180;
0546     }
0547 
0548     // 0 angle means that the angle is actually 90 degrees
0549     // From docs: Zero angle represents the vector from bottom to top. [MS-ODRAW:fillAngle], p.198
0550     angle = (angle + 90) % 360;
0551 
0552     qreal cosA = cos(angle * M_PI / 180);
0553     qreal sinA = sin(angle * M_PI / 180);
0554 
0555     if ((angle >= 0 && angle < 45) || (angle >= 315 && angle <= 360)) {
0556         dx = 50;
0557         dy = sinA/cosA * 50;
0558     } else if (angle >= 45 && angle < 135) {
0559         dy = 50;
0560         dx = cosA/sinA * 50;
0561     } else if  (angle >= 135 && angle < 225) {
0562         dx = -50;
0563         dy = sinA/cosA*(-50);
0564     } else {
0565         dy = -50;
0566         dx = cosA/sinA * (-50);
0567     }
0568 
0569     style.addAttribute("svg:spreadMethod", "reflect");
0570 
0571     int x1 = 50 - dx;
0572     int y1 = 50 + dy;
0573     int x2 = 50 + dx;
0574     int y2 = 50 - dy;
0575 
0576     if (ds.fillFocus() == 100) {
0577         qSwap(x1,x2);
0578         qSwap(y1,y2);
0579     } else if (ds.fillFocus() == 50) {
0580         int sx = (x2 - x1) * 0.5;
0581         int sy = (y2 - y1) * 0.5;
0582         x2 = x1 +  sx;
0583         y2 = y1 +  sy;
0584 
0585         // in one case don't swap the gradient vector
0586         if (angle != 90) {
0587             qSwap(x1,x2);
0588             qSwap(y1,y2);
0589         }
0590     } else if (ds.fillFocus() == -50) {
0591         int sx = (x2 - x1) * 0.5;
0592         int sy = (y2 - y1) * 0.5;
0593         x2 = x1 + sx;
0594         y2 = y1 + sy;
0595         // in this case we have to swap the gradient vector
0596         // check some gradient file from MS Office 2003
0597         if (angle == 90) {
0598             qSwap(x1,x2);
0599             qSwap(y1,y2);
0600         }
0601     }
0602 
0603     QBuffer writerBuffer;
0604     writerBuffer.open(QIODevice::WriteOnly);
0605     KoXmlWriter elementWriter(&writerBuffer);
0606 
0607     qreal fillOpacity = toQReal(ds.fillOpacity());
0608     qreal fillBackOpacity = toQReal(ds.fillBackOpacity());
0609     // if fillShadeColors() is not empty use the colors and points defined inside
0610     // if it is empty use the colors defined inside fillColor() and fillBackColor
0611 
0612     if (ds.fillShadeColors()) {
0613         style.addAttribute("svg:x1", QString("%1%").arg(x1));
0614         style.addAttribute("svg:y1", QString("%1%").arg(y1));
0615         style.addAttribute("svg:x2", QString("%1%").arg(x2));
0616         style.addAttribute("svg:y2", QString("%1%").arg(y2));
0617 
0618         IMsoArray a = ds.fillShadeColors_complex();
0619 
0620         QBuffer streamBuffer(&a.data);
0621         streamBuffer.open(QIODevice::ReadOnly);
0622         LEInputStream in(&streamBuffer);
0623 
0624         OfficeArtCOLORREF color;
0625         FixedPoint fixedPoint;
0626         for (int i = 0; i < a.nElems; i++) {
0627             try {
0628                 parseOfficeArtCOLORREF(in,color);
0629             } catch (const IOException& e) {
0630                 qDebug() << e.msg;
0631                 break;
0632             } catch (...) {
0633                 qDebug() << "Warning: Caught an unknown exception!";
0634                 break;
0635             }
0636             try {
0637                 parseFixedPoint(in,fixedPoint);
0638             } catch (const IOException& e) {
0639                 qDebug() << e.msg;
0640                 break;
0641             } catch (...) {
0642                 qDebug() << "Warning: Caught an unknown exception!";
0643                 break;
0644             }
0645 
0646             qreal offset = toQReal(fixedPoint);
0647             elementWriter.startElement("svg:stop");
0648             elementWriter.addAttribute("svg:offset", QString("%1").arg(offset));
0649             elementWriter.addAttribute("svg:stop-color", processOfficeArtCOLORREF(color, ds).name());
0650             qreal opacity = ((1.0 - offset) * fillBackOpacity + offset * fillOpacity);
0651             if (opacity != 1.0) {
0652                 elementWriter.addAttribute("svg:stop-opacity", opacity);
0653             }
0654             elementWriter.endElement();
0655         }
0656         streamBuffer.close();
0657     } else {
0658         QColor fillColor = processOfficeArtCOLORREF(ds.fillColor(), ds);
0659         QColor backColor = processOfficeArtCOLORREF(ds.fillBackColor(), ds);
0660 
0661         if (ds.fillFocus() == 50){
0662             if (toQReal( ds.fillAngle() ) > 0){
0663                 qSwap(x1,x2);
0664                 qSwap(y1,y2);
0665             }
0666         }
0667 
0668         style.addAttribute("svg:x1", QString("%1%").arg(x1));
0669         style.addAttribute("svg:y1", QString("%1%").arg(y1));
0670         style.addAttribute("svg:x2", QString("%1%").arg(x2));
0671         style.addAttribute("svg:y2", QString("%1%").arg(y2));
0672 
0673         elementWriter.startElement("svg:stop");
0674         elementWriter.addAttribute("svg:offset", "0");
0675         elementWriter.addAttribute("svg:stop-color", fillColor.name());
0676         if (fillOpacity != 1.0) {
0677             elementWriter.addAttribute("svg:stop-opacity", fillOpacity);
0678         }
0679         elementWriter.endElement();
0680 
0681         elementWriter.startElement("svg:stop");
0682         elementWriter.addAttribute("svg:offset", "1");
0683         elementWriter.addAttribute("svg:stop-color", backColor.name());
0684         if (fillBackOpacity != 1.0) {
0685             elementWriter.addAttribute("svg:stop-opacity", fillBackOpacity);
0686         }
0687         elementWriter.endElement();
0688     }
0689 
0690     QString elementContents = QString::fromUtf8(writerBuffer.buffer(), writerBuffer.buffer().size());
0691     style.addChildElement("svg:stop", elementContents);
0692 }
0693 
0694 QString ODrawToOdf::defineDashStyle(KoGenStyles& styles, const quint32 lineDashing)
0695 {
0696     if (lineDashing <= 0 || lineDashing > 10) {
0697         return QString();
0698     }
0699 
0700     KoGenStyle strokeDash(KoGenStyle::StrokeDashStyle);
0701     switch (lineDashing) {
0702     case msolineSolid:
0703         break;
0704     case msolineDashSys:
0705         strokeDash.addAttribute("draw:dots1", "1");
0706         strokeDash.addAttribute("draw:dots1-length", "300%");
0707         strokeDash.addAttribute("draw:distance", "100%");
0708         break;
0709     case msolineDotSys:
0710         strokeDash.addAttribute("draw:dots1", "1");
0711         strokeDash.addAttribute("draw:dots1-length", "200%");
0712         break;
0713     case msolineDashDotSys:
0714         strokeDash.addAttribute("draw:dots1", "1");
0715         strokeDash.addAttribute("draw:dots1-length", "300%");
0716         strokeDash.addAttribute("draw:dots2", "1");
0717         strokeDash.addAttribute("draw:dots2-length", "100%");
0718         break;
0719     case msolineDashDotDotSys:
0720         strokeDash.addAttribute("draw:dots1", "1");
0721         strokeDash.addAttribute("draw:dots1-length", "300%");
0722         strokeDash.addAttribute("draw:dots2", "1");
0723         strokeDash.addAttribute("draw:dots2-length", "100%");
0724         break;
0725     case msolineDotGEL:
0726         strokeDash.addAttribute("draw:dots1", "1");
0727         strokeDash.addAttribute("draw:dots1-length", "100%");
0728         break;
0729     case msolineDashGEL:
0730         strokeDash.addAttribute("draw:dots1", "4");
0731         strokeDash.addAttribute("draw:dots1-length", "100%");
0732         break;
0733     case msolineLongDashGEL:
0734         strokeDash.addAttribute("draw:dots1", "8");
0735         strokeDash.addAttribute("draw:dots1-length", "100%");
0736         break;
0737     case msolineDashDotGEL:
0738         strokeDash.addAttribute("draw:dots1", "1");
0739         strokeDash.addAttribute("draw:dots1-length", "300%");
0740         strokeDash.addAttribute("draw:dots2", "1");
0741         strokeDash.addAttribute("draw:dots2-length", "100%");
0742         break;
0743     case msolineLongDashDotGEL:
0744         strokeDash.addAttribute("draw:dots1", "1");
0745         strokeDash.addAttribute("draw:dots1-length", "800%");
0746         strokeDash.addAttribute("draw:dots2", "1");
0747         strokeDash.addAttribute("draw:dots2-length", "100%");
0748         break;
0749     case msolineLongDashDotDotGEL:
0750         strokeDash.addAttribute("draw:dots1", "1");
0751         strokeDash.addAttribute("draw:dots1-length", "800%");
0752         strokeDash.addAttribute("draw:dots2", "2");
0753         strokeDash.addAttribute("draw:dots2-length", "100%");
0754         break;
0755     };
0756 
0757     if (lineDashing < 5) {
0758         strokeDash.addAttribute("draw:distance", "100%");
0759     } else {
0760         strokeDash.addAttribute("draw:distance", "300%");
0761     }
0762     return styles.insert(strokeDash, QString("Dash_20_%1").arg(lineDashing),
0763                          KoGenStyles::DontAddNumberToName);
0764 }
0765 
0766 QColor ODrawToOdf::processOfficeArtCOLORREF(const MSO::OfficeArtCOLORREF& c, const DrawStyle& ds)
0767 {
0768     static const QRgb systemColors[25] = {
0769         0xc0c0c0, 0x008080, 0x000080, 0x808080, 0xc0c0c0, 0xffffff, 0x000000,
0770         0x000000, 0x000000, 0xffffff, 0xc0c0c0, 0xc0c0c0, 0x808080, 0x000080,
0771         0xffffff, 0xc0c0c0, 0x808080, 0x808080, 0x000000, 0xc0c0c0, 0xffffff,
0772         0x000000, 0xc0c0c0, 0x000000, 0xffffc0
0773     };
0774     //TODO: implement all cases!!!
0775     QColor ret;
0776     MSO::OfficeArtCOLORREF tmp;
0777 
0778     // A value of 0x1 specifies that green and red will be treated as an
0779     // unsigned 16-bit index into the system color table.  Values less than
0780     // 0x00F0 map directly to system colors.  Table [1] specifies values that
0781     // have special meaning, [1] MS-ODRAW 2.2.2
0782     if (c.fSysIndex) {
0783         if (c.red >= 0xF0) {
0784             switch (c.red) {
0785             // Use the fill color of the shape.
0786             case 0xF0:
0787                 tmp = ds.fillColor();
0788                 break;
0789             // If the shape contains a line, use the line color of the
0790             // shape. Otherwise, use the fill color.
0791             case 0xF1:
0792             {
0793                 if (ds.fLine()) {
0794                     tmp = ds.lineColor();
0795                 } else {
0796                     tmp = ds.fillColor();
0797                 }
0798                 break;
0799             }
0800             // Use the line color of the shape.
0801             case 0xF2:
0802                 tmp = ds.lineColor();
0803                 break;
0804             // Use the shadow color of the shape.
0805             case 0xF3:
0806                 tmp = ds.shadowColor();
0807                 break;
0808             // TODO: Use the current, or last-used, color.
0809             case 0xF4:
0810                 qWarning() << "red: Unhandled fSysIndex 0xF4!";
0811                 break;
0812             // Use the fill background color of the shape.
0813             case 0xF5:
0814                 tmp  = ds.fillBackColor();
0815                 break;
0816             // TODO: Use the line background color of the shape.
0817             case 0xF6:
0818                 qWarning() << "red: Unhandled fSysIndex 0xF6!";
0819                 break;
0820             // If the shape contains a fill, use the fill color of the
0821             // shape. Otherwise, use the line color.
0822             case 0xF7:
0823             {
0824                 if (ds.fFilled()) {
0825                     tmp = ds.fillColor();
0826                 } else {
0827                     tmp = ds.lineColor();
0828                 }
0829                 break;
0830             }
0831             default:
0832                 qWarning() << "red: Unhandled fSysIndex!" << c.red;
0833                 break;
0834             }
0835         } else if (c.green == 0) {
0836             tmp = c;
0837             // system colors
0838             if (c.red < 25) {
0839                 const QRgb& col = systemColors[c.red];
0840                 tmp.red = qRed(col);
0841                 tmp.green = qGreen(col);
0842                 tmp.blue = qBlue(col);
0843             } else {
0844                 qWarning() << "red: Unhandled system color" << c.red;
0845             }
0846         }
0847 
0848         ret = client->toQColor(tmp);
0849         qreal p = c.blue / (qreal) 255;
0850 
0851         switch (c.green & 0xF) {
0852         case 0x00: break; // do nothing
0853         // Darken the color by the value that is specified in the blue field.
0854         // A blue value of 0xFF specifies that the color is to be left
0855         // unchanged, whereas a blue value of 0x00 specifies that the color is
0856         // to be completely darkened.
0857         case 0x01:
0858         {
0859             if (c.blue == 0x00) {
0860                 ret = ret.darker(800);
0861             } else if (c.blue != 0xFF) {
0862                 ret.setRed(ceil(p * ret.red()));
0863                 ret.setGreen(ceil(p * ret.green()));
0864                 ret.setBlue(ceil(p * ret.blue()));
0865             }
0866             break;
0867         }
0868         // Lighten the color by the value that is specified in the blue field.
0869         // A blue value of 0xFF specifies that the color is to be left
0870         // unchanged, whereas a blue value of 0x00 specifies that the color is
0871         // to be completely lightened.
0872         case 0x02:
0873         {
0874             if (c.blue == 0x00) {
0875                 ret = ret.lighter(150);
0876             } else if (c.blue != 0xFF) {
0877                 ret.setRed(ret.red() + ceil(p * ret.red()));
0878                 ret.setGreen(ret.green() + ceil(p * ret.green()));
0879                 ret.setBlue(ret.blue() + ceil(p * ret.blue()));
0880             }
0881             break;
0882     }
0883         //TODO:
0884         case 0x03:
0885         case 0x04:
0886         case 0x05:
0887         case 0x06:
0888         default:
0889             qWarning() << "green: Unhandled fSysIndex!" << c.green;
0890             break;
0891         }
0892         // TODO
0893         if (c.green & 0x20) {
0894             qWarning() << "green: unhandled 0x20";
0895         }
0896         if (c.green & 0x40) {
0897             qWarning() << "green: unhandled 0x40";
0898         }
0899         if (c.green & 0x80) {
0900             qWarning() << "green: unhandled 0x80";
0901         }
0902     } else {
0903         ret = client->toQColor(c);
0904     }
0905     return ret;
0906 }
0907 
0908 const char* getFillRule(quint16 shapeType)
0909 {
0910     switch (shapeType) {
0911     case msosptDonut:
0912     case msosptNoSmoking:
0913     case msosptActionButtonBlank:
0914     case msosptActionButtonHome:
0915     case msosptActionButtonHelp:
0916     case msosptActionButtonInformation:
0917     case msosptActionButtonForwardNext:
0918     case msosptActionButtonBackPrevious:
0919     case msosptActionButtonEnd:
0920     case msosptActionButtonBeginning:
0921     case msosptActionButtonReturn:
0922     case msosptActionButtonDocument:
0923     case msosptActionButtonSound:
0924     case msosptActionButtonMovie:
0925         return "evenodd";
0926     default:
0927         return "";
0928     }
0929 }
0930 
0931 const char* getFillType(quint32 fillType)
0932 {
0933     switch (fillType) {
0934     case msofillPattern:
0935         // NOTE: there's usually a DIB file used for the pattern, check also
0936         // draw:fill="hatch" and <draw:hatch> in ODF specification
0937     case msofillTexture:
0938     case msofillPicture:
0939         return "bitmap";
0940     case msofillShade:
0941     case msofillShadeCenter:
0942     case msofillShadeShape:
0943     case msofillShadeScale:
0944     case msofillShadeTitle:
0945         return "gradient";
0946     case msofillBackground:
0947         return "none";
0948     case msofillSolid:
0949     default:
0950         return "solid";
0951     }
0952 }
0953 
0954 const char* getRepeatStyle(quint32 fillType)
0955 {
0956     switch (fillType) {
0957     case msofillPicture:
0958     case msofillShadeScale:
0959         return "stretch";
0960     case msofillSolid:
0961     case msofillShade:
0962     case msofillShadeCenter:
0963     case msofillShadeShape:
0964     case msofillShadeTitle:
0965     case msofillBackground:
0966         return "no-repeat";
0967     case msofillPattern:
0968     case msofillTexture:
0969     default:
0970         return "repeat";
0971     }
0972 }
0973 
0974 const char* getGradientRendering(quint32 fillType)
0975 {
0976     //TODO: Add the logic!!!
0977     switch (fillType) {
0978     case msofillSolid:
0979     case msofillPattern:
0980     case msofillTexture:
0981     case msofillPicture:
0982     case msofillShade:
0983     case msofillShadeCenter:
0984     case msofillShadeShape:
0985     case msofillShadeScale:
0986     case msofillShadeTitle:
0987     case msofillBackground:
0988     default:
0989         return "axial";
0990     }
0991 }
0992 
0993 const char* getHorizontalPos(quint32 posH)
0994 {
0995     switch (posH) {
0996     case 0: // msophAbs
0997         return "from-left";
0998     case 1: // msophLeft
0999         return "left";
1000     case 2: // msophCenter
1001         return "center";
1002     case 3: // msophRight
1003         return "right";
1004     case 4: // msophInside
1005         return "inside";
1006     case 5: // msophOutside
1007         return "outside";
1008     default:
1009         return "from-left";
1010     }
1011 }
1012 
1013 const char* getHorizontalRel(quint32 posRelH)
1014 {
1015     switch (posRelH) {
1016     case 0: //msoprhMargin
1017         return "page-content";
1018     case 1: //msoprhPage
1019         return "page";
1020     case 2: //msoprhText
1021         return "paragraph";
1022     case 3: //msoprhChar
1023         return "char";
1024     default:
1025         return "page-content";
1026     }
1027 }
1028 
1029 const char* getVerticalPos(quint32 posV)
1030 {
1031     switch (posV) {
1032     case 0: // msophAbs
1033         return "from-top";
1034     case 1: // msophTop
1035         return "top";
1036     case 2: // msophCenter
1037         return "middle";
1038     case 3: // msophBottom
1039         return "bottom";
1040     case 4: // msophInside - not compatible with ODF
1041         return "top";
1042     case 5: // msophOutside - not compatible with ODF
1043         return "bottom";
1044     default:
1045         return "from-top";
1046     }
1047 }
1048 
1049 const char* getVerticalRel(quint32 posRelV)
1050 {
1051     switch (posRelV) {
1052     case 0: //msoprvMargin
1053         return "page-content";
1054     case 1: //msoprvPage
1055         return "page";
1056     case 2: //msoprvText
1057         return "paragraph";
1058     case 3: //msoprvLine
1059         return "char";
1060     default:
1061         return "page-content";
1062     }
1063 }
1064 
1065 const char* getHorizontalAlign(quint32 anchorText)
1066 {
1067     switch (anchorText) {
1068     case msoanchorTop:
1069     case msoanchorTopBaseline:
1070     case msoanchorMiddle:
1071     case msoanchorBottom:
1072     case msoanchorBottomBaseline:
1073         return "left";
1074     case msoanchorTopCentered:
1075     case msoanchorTopCenteredBaseline:
1076     case msoanchorMiddleCentered:
1077     case msoanchorBottomCentered:
1078     case msoanchorBottomCenteredBaseline:
1079         return "justify";
1080     default:
1081         return "left";
1082     }
1083 }
1084 
1085 const char* getVerticalAlign(quint32 anchorText)
1086 {
1087     switch (anchorText) {
1088     case msoanchorTop:
1089     case msoanchorTopCentered:
1090     case msoanchorTopBaseline: //not compatible with ODF
1091     case msoanchorTopCenteredBaseline: //not compatible with ODF
1092         return "top";
1093     case msoanchorMiddle:
1094     case msoanchorMiddleCentered:
1095         return "middle";
1096     case msoanchorBottom:
1097     case msoanchorBottomCentered:
1098     case msoanchorBottomBaseline: //not compatible with ODF
1099     case msoanchorBottomCenteredBaseline: //not compatible with ODF
1100         return "bottom";
1101     default:
1102         return "top";
1103     }
1104 }
1105 
1106 const char* getStrokeLineCap(quint32 capStyle)
1107 {
1108     switch (capStyle) {
1109     case msolineEndCapRound:
1110         return "round";
1111     case msolineEndCapSquare:
1112         return "square";
1113     case msolineEndCapFlat:
1114     default:
1115         return "butt";
1116     }
1117 }
1118 
1119 const char* getStrokeLineJoin(quint32 joinStyle)
1120 {
1121     switch (joinStyle) {
1122     case msolineJoinBevel:
1123         return "bevel";
1124     case msolineJoinMiter:
1125         return "miter";
1126     case msolineJoinRound:
1127     default:
1128         return "round";
1129     }
1130 }