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 }