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 }