File indexing completed on 2024-12-08 12:55:55

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