1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ 2 /* 3 * This file is part of the LibreOffice project. 4 * 5 * This Source Code Form is subject to the terms of the Mozilla Public 6 * License, v. 2.0. If a copy of the MPL was not distributed with this 7 * file, You can obtain one at http://mozilla.org/MPL/2.0/. 8 * 9 * This file incorporates work covered by the following license notice: 10 * 11 * Licensed to the Apache Software Foundation (ASF) under one or more 12 * contributor license agreements. See the NOTICE file distributed 13 * with this work for additional information regarding copyright 14 * ownership. The ASF licenses this file to you under the Apache 15 * License, Version 2.0 (the "License"); you may not use this file 16 * except in compliance with the License. You may obtain a copy of 17 * the License at http://www.apache.org/licenses/LICENSE-2.0 . 18 */ 19 20 #include <drawinglayer/geometry/viewinformation2d.hxx> 21 #include <drawinglayer/primitive2d/borderlineprimitive2d.hxx> 22 #include <drawinglayer/primitive2d/drawinglayer_primitivetypes2d.hxx> 23 #include <basegfx/polygon/b2dpolygon.hxx> 24 #include <drawinglayer/primitive2d/polygonprimitive2d.hxx> 25 #include <drawinglayer/primitive2d/PolyPolygonColorPrimitive2D.hxx> 26 #include <rtl/math.hxx> 27 28 #include <algorithm> 29 30 31 namespace drawinglayer::primitive2d 32 { BorderLine(const drawinglayer::attribute::LineAttribute & rLineAttribute,double fStartLeft,double fStartRight,double fEndLeft,double fEndRight)33 BorderLine::BorderLine( 34 const drawinglayer::attribute::LineAttribute& rLineAttribute, 35 double fStartLeft, 36 double fStartRight, 37 double fEndLeft, 38 double fEndRight) 39 : maLineAttribute(rLineAttribute), 40 mfStartLeft(fStartLeft), 41 mfStartRight(fStartRight), 42 mfEndLeft(fEndLeft), 43 mfEndRight(fEndRight), 44 mbIsGap(false) 45 { 46 } 47 BorderLine(double fWidth)48 BorderLine::BorderLine( 49 double fWidth) 50 : maLineAttribute(basegfx::BColor(), fWidth), 51 mfStartLeft(0.0), 52 mfStartRight(0.0), 53 mfEndLeft(0.0), 54 mfEndRight(0.0), 55 mbIsGap(true) 56 { 57 } 58 ~BorderLine()59 BorderLine::~BorderLine() 60 { 61 } 62 operator ==(const BorderLine & rBorderLine) const63 bool BorderLine::operator==(const BorderLine& rBorderLine) const 64 { 65 return getLineAttribute() == rBorderLine.getLineAttribute() 66 && getStartLeft() == rBorderLine.getStartLeft() 67 && getStartRight() == rBorderLine.getStartRight() 68 && getEndLeft() == rBorderLine.getEndLeft() 69 && getEndRight() == rBorderLine.getEndRight() 70 && isGap() == rBorderLine.isGap(); 71 } 72 73 // helper to add a centered, maybe stroked line primitive to rContainer addPolygonStrokePrimitive2D(Primitive2DContainer & rContainer,const basegfx::B2DPoint & rStart,const basegfx::B2DPoint & rEnd,const attribute::LineAttribute & rLineAttribute,const attribute::StrokeAttribute & rStrokeAttribute)74 static void addPolygonStrokePrimitive2D( 75 Primitive2DContainer& rContainer, 76 const basegfx::B2DPoint& rStart, 77 const basegfx::B2DPoint& rEnd, 78 const attribute::LineAttribute& rLineAttribute, 79 const attribute::StrokeAttribute& rStrokeAttribute) 80 { 81 basegfx::B2DPolygon aPolygon; 82 83 aPolygon.append(rStart); 84 aPolygon.append(rEnd); 85 86 if (rStrokeAttribute.isDefault()) 87 { 88 rContainer.push_back( 89 new PolygonStrokePrimitive2D( 90 aPolygon, 91 rLineAttribute)); 92 } 93 else 94 { 95 rContainer.push_back( 96 new PolygonStrokePrimitive2D( 97 aPolygon, 98 rLineAttribute, 99 rStrokeAttribute)); 100 } 101 } 102 getFullWidth() const103 double BorderLinePrimitive2D::getFullWidth() const 104 { 105 double fRetval(0.0); 106 107 for(const auto& candidate : maBorderLines) 108 { 109 fRetval += candidate.getLineAttribute().getWidth(); 110 } 111 112 return fRetval; 113 } 114 create2DDecomposition(Primitive2DContainer & rContainer,const geometry::ViewInformation2D &) const115 void BorderLinePrimitive2D::create2DDecomposition(Primitive2DContainer& rContainer, const geometry::ViewInformation2D& /*rViewInformation*/) const 116 { 117 if (getStart().equal(getEnd()) || getBorderLines().empty()) 118 return; 119 120 // get data and vectors 121 basegfx::B2DVector aVector(getEnd() - getStart()); 122 aVector.normalize(); 123 const basegfx::B2DVector aPerpendicular(basegfx::getPerpendicular(aVector)); 124 const double fFullWidth(getFullWidth()); 125 double fOffset(fFullWidth * -0.5); 126 127 for(const auto& candidate : maBorderLines) 128 { 129 const double fWidth(candidate.getLineAttribute().getWidth()); 130 131 if(!candidate.isGap()) 132 { 133 const basegfx::B2DVector aDeltaY(aPerpendicular * (fOffset + (fWidth * 0.5))); 134 const basegfx::B2DPoint aStart(getStart() + aDeltaY); 135 const basegfx::B2DPoint aEnd(getEnd() + aDeltaY); 136 const bool bStartPerpendicular(rtl::math::approxEqual(candidate.getStartLeft(), candidate.getStartRight())); 137 const bool bEndPerpendicular(rtl::math::approxEqual(candidate.getEndLeft(), candidate.getEndRight())); 138 139 if(bStartPerpendicular && bEndPerpendicular) 140 { 141 // start and end extends lead to an edge perpendicular to the line, so we can just use 142 // a PolygonStrokePrimitive2D for representation 143 addPolygonStrokePrimitive2D( 144 rContainer, 145 aStart - (aVector * candidate.getStartLeft()), 146 aEnd + (aVector * candidate.getEndLeft()), 147 candidate.getLineAttribute(), 148 getStrokeAttribute()); 149 } 150 else 151 { 152 // start and/or end extensions lead to a lineStart/End that is *not* 153 // perpendicular to the line itself 154 if(getStrokeAttribute().isDefault() || 0.0 == getStrokeAttribute().getFullDotDashLen()) 155 { 156 // without stroke, we can simply represent that using a filled polygon 157 const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); 158 basegfx::B2DPolygon aPolygon; 159 160 aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); 161 aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); 162 aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); 163 aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); 164 165 rContainer.push_back( 166 new PolyPolygonColorPrimitive2D( 167 basegfx::B2DPolyPolygon(aPolygon), 168 candidate.getLineAttribute().getColor())); 169 } 170 else 171 { 172 // with stroke, we have a problem - a filled polygon would lose the 173 // stroke. Let's represent the start and/or end as triangles, the main 174 // line still as PolygonStrokePrimitive2D. 175 // Fill default line Start/End for stroke, so we need no adaptations in else paths 176 basegfx::B2DPoint aStrokeStart(aStart - (aVector * candidate.getStartLeft())); 177 basegfx::B2DPoint aStrokeEnd(aEnd + (aVector * candidate.getEndLeft())); 178 const basegfx::B2DVector aHalfLineOffset(aPerpendicular * (candidate.getLineAttribute().getWidth() * 0.5)); 179 180 if(!bStartPerpendicular) 181 { 182 const double fMin(std::min(candidate.getStartLeft(), candidate.getStartRight())); 183 const double fMax(std::max(candidate.getStartLeft(), candidate.getStartRight())); 184 basegfx::B2DPolygon aPolygon; 185 186 // create a triangle with min/max values for LineStart and add 187 if(rtl::math::approxEqual(candidate.getStartLeft(), fMax)) 188 { 189 aPolygon.append(aStart - aHalfLineOffset - (aVector * candidate.getStartLeft())); 190 } 191 192 aPolygon.append(aStart - aHalfLineOffset - (aVector * fMin)); 193 aPolygon.append(aStart + aHalfLineOffset - (aVector * fMin)); 194 195 if(rtl::math::approxEqual(candidate.getStartRight(), fMax)) 196 { 197 aPolygon.append(aStart + aHalfLineOffset - (aVector * candidate.getStartRight())); 198 } 199 200 rContainer.push_back( 201 new PolyPolygonColorPrimitive2D( 202 basegfx::B2DPolyPolygon(aPolygon), 203 candidate.getLineAttribute().getColor())); 204 205 // Adapt StrokeStart accordingly 206 aStrokeStart = aStart - (aVector * fMin); 207 } 208 209 if(!bEndPerpendicular) 210 { 211 const double fMin(std::min(candidate.getEndLeft(), candidate.getEndRight())); 212 const double fMax(std::max(candidate.getEndLeft(), candidate.getEndRight())); 213 basegfx::B2DPolygon aPolygon; 214 215 // create a triangle with min/max values for LineEnd and add 216 if(rtl::math::approxEqual(candidate.getEndLeft(), fMax)) 217 { 218 aPolygon.append(aEnd - aHalfLineOffset + (aVector * candidate.getEndLeft())); 219 } 220 221 if(rtl::math::approxEqual(candidate.getEndRight(), fMax)) 222 { 223 aPolygon.append(aEnd + aHalfLineOffset + (aVector * candidate.getEndRight())); 224 } 225 226 aPolygon.append(aEnd + aHalfLineOffset + (aVector * fMin)); 227 aPolygon.append(aEnd - aHalfLineOffset + (aVector * fMin)); 228 229 rContainer.push_back( 230 new PolyPolygonColorPrimitive2D( 231 basegfx::B2DPolyPolygon(aPolygon), 232 candidate.getLineAttribute().getColor())); 233 234 // Adapt StrokeEnd accordingly 235 aStrokeEnd = aEnd + (aVector * fMin); 236 } 237 238 addPolygonStrokePrimitive2D( 239 rContainer, 240 aStrokeStart, 241 aStrokeEnd, 242 candidate.getLineAttribute(), 243 getStrokeAttribute()); 244 } 245 } 246 } 247 248 fOffset += fWidth; 249 } 250 } 251 isHorizontalOrVertical(const geometry::ViewInformation2D & rViewInformation) const252 bool BorderLinePrimitive2D::isHorizontalOrVertical(const geometry::ViewInformation2D& rViewInformation) const 253 { 254 if (!getStart().equal(getEnd())) 255 { 256 const basegfx::B2DHomMatrix& rOTVT = rViewInformation.getObjectToViewTransformation(); 257 const basegfx::B2DVector aVector(rOTVT * getEnd() - rOTVT * getStart()); 258 259 return basegfx::fTools::equalZero(aVector.getX()) || basegfx::fTools::equalZero(aVector.getY()); 260 } 261 262 return false; 263 } 264 BorderLinePrimitive2D(const basegfx::B2DPoint & rStart,const basegfx::B2DPoint & rEnd,const std::vector<BorderLine> & rBorderLines,const drawinglayer::attribute::StrokeAttribute & rStrokeAttribute)265 BorderLinePrimitive2D::BorderLinePrimitive2D( 266 const basegfx::B2DPoint& rStart, 267 const basegfx::B2DPoint& rEnd, 268 const std::vector< BorderLine >& rBorderLines, 269 const drawinglayer::attribute::StrokeAttribute& rStrokeAttribute) 270 : BufferedDecompositionPrimitive2D(), 271 maStart(rStart), 272 maEnd(rEnd), 273 maBorderLines(rBorderLines), 274 maStrokeAttribute(rStrokeAttribute) 275 { 276 } 277 operator ==(const BasePrimitive2D & rPrimitive) const278 bool BorderLinePrimitive2D::operator==(const BasePrimitive2D& rPrimitive) const 279 { 280 if(BufferedDecompositionPrimitive2D::operator==(rPrimitive)) 281 { 282 const BorderLinePrimitive2D& rCompare = static_cast<const BorderLinePrimitive2D&>(rPrimitive); 283 284 if (getStart() == rCompare.getStart() 285 && getEnd() == rCompare.getEnd() 286 && getStrokeAttribute() == rCompare.getStrokeAttribute()) 287 { 288 if (getBorderLines().size() == rCompare.getBorderLines().size()) 289 { 290 for (size_t a(0); a < getBorderLines().size(); a++) 291 { 292 if (!(getBorderLines()[a] == rCompare.getBorderLines()[a])) 293 { 294 return false; 295 } 296 } 297 } 298 } 299 } 300 301 return false; 302 } 303 304 // provide unique ID ImplPrimitive2DIDBlock(BorderLinePrimitive2D,PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D)305 ImplPrimitive2DIDBlock(BorderLinePrimitive2D, PRIMITIVE2D_ID_BORDERLINEPRIMITIVE2D) 306 307 Primitive2DReference tryMergeBorderLinePrimitive2D( 308 const BorderLinePrimitive2D* pCandidateA, 309 const BorderLinePrimitive2D* pCandidateB) 310 { 311 assert(pCandidateA); 312 assert(pCandidateB); 313 314 // start of candidate has to match end of this 315 if(!pCandidateA->getEnd().equal(pCandidateB->getStart())) 316 { 317 return Primitive2DReference(); 318 } 319 320 // candidate A needs a length 321 if(pCandidateA->getStart().equal(pCandidateA->getEnd())) 322 { 323 return Primitive2DReference(); 324 } 325 326 // candidate B needs a length 327 if(pCandidateB->getStart().equal(pCandidateB->getEnd())) 328 { 329 return Primitive2DReference(); 330 } 331 332 // StrokeAttribute has to be equal 333 if(!(pCandidateA->getStrokeAttribute() == pCandidateB->getStrokeAttribute())) 334 { 335 return Primitive2DReference(); 336 } 337 338 // direction has to be equal -> cross product == 0.0 339 const basegfx::B2DVector aVT(pCandidateA->getEnd() - pCandidateA->getStart()); 340 const basegfx::B2DVector aVC(pCandidateB->getEnd() - pCandidateB->getStart()); 341 if(!rtl::math::approxEqual(0.0, aVC.cross(aVT))) 342 { 343 return Primitive2DReference(); 344 } 345 346 // number BorderLines has to be equal 347 const size_t count(pCandidateA->getBorderLines().size()); 348 if(count != pCandidateB->getBorderLines().size()) 349 { 350 return Primitive2DReference(); 351 } 352 353 for(size_t a(0); a < count; a++) 354 { 355 const BorderLine& rBT(pCandidateA->getBorderLines()[a]); 356 const BorderLine& rBC(pCandidateB->getBorderLines()[a]); 357 358 // LineAttribute has to be the same 359 if(!(rBC.getLineAttribute() == rBT.getLineAttribute())) 360 { 361 return Primitive2DReference(); 362 } 363 364 // isGap has to be the same 365 if(rBC.isGap() != rBT.isGap()) 366 { 367 return Primitive2DReference(); 368 } 369 370 if(rBT.isGap()) 371 { 372 // when gap, width has to be equal 373 if(!rtl::math::approxEqual(rBT.getLineAttribute().getWidth(), rBC.getLineAttribute().getWidth())) 374 { 375 return Primitive2DReference(); 376 } 377 } 378 else 379 { 380 // when not gap, the line extends have at least reach to the center ( > 0.0), 381 // else there is an extend usage. When > 0.0 they just overlap, no problem 382 if(rBT.getEndLeft() >= 0.0 383 && rBT.getEndRight() >= 0.0 384 && rBC.getStartLeft() >= 0.0 385 && rBC.getStartRight() >= 0.0) 386 { 387 // okay 388 } 389 else 390 { 391 return Primitive2DReference(); 392 } 393 } 394 } 395 396 // all conditions met, create merged primitive 397 std::vector< BorderLine > aMergedBorderLines; 398 399 for(size_t a(0); a < count; a++) 400 { 401 const BorderLine& rBT(pCandidateA->getBorderLines()[a]); 402 const BorderLine& rBC(pCandidateB->getBorderLines()[a]); 403 404 if(rBT.isGap()) 405 { 406 aMergedBorderLines.push_back(rBT); 407 } 408 else 409 { 410 aMergedBorderLines.push_back( 411 BorderLine( 412 rBT.getLineAttribute(), 413 rBT.getStartLeft(), rBT.getStartRight(), 414 rBC.getEndLeft(), rBC.getEndRight())); 415 } 416 } 417 418 return Primitive2DReference( 419 new BorderLinePrimitive2D( 420 pCandidateA->getStart(), 421 pCandidateB->getEnd(), 422 aMergedBorderLines, 423 pCandidateA->getStrokeAttribute())); 424 } 425 426 } // end of namespace 427 428 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */ 429