1// Copyright 2014 PDFium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com 6 7#include "xfa/fxgraphics/cfx_graphics.h" 8 9#include <memory> 10 11#include "core/fxge/cfx_fxgedevice.h" 12#include "core/fxge/cfx_gemodule.h" 13#include "core/fxge/cfx_renderdevice.h" 14#include "core/fxge/cfx_unicodeencoding.h" 15#include "third_party/base/ptr_util.h" 16#include "xfa/fxgraphics/cfx_color.h" 17#include "xfa/fxgraphics/cfx_path.h" 18#include "xfa/fxgraphics/cfx_pattern.h" 19#include "xfa/fxgraphics/cfx_shading.h" 20 21namespace { 22 23enum { 24 FX_CONTEXT_None = 0, 25 FX_CONTEXT_Device, 26}; 27 28#define FX_HATCHSTYLE_Total 53 29 30struct FX_HATCHDATA { 31 int32_t width; 32 int32_t height; 33 uint8_t maskBits[64]; 34}; 35 36const FX_HATCHDATA hatchBitmapData[FX_HATCHSTYLE_Total] = { 37 {16, // Horizontal 38 16, 39 { 40 0xff, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 41 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 42 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xff, 43 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 44 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 45 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 46 }}, 47 {16, // Vertical 48 16, 49 { 50 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 51 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 52 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 53 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 54 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 55 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 56 }}, 57 {16, // ForwardDiagonal 58 16, 59 { 60 0x80, 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 61 0x00, 0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04, 62 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x80, 63 0x80, 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x20, 0x20, 0x00, 0x00, 64 0x10, 0x10, 0x00, 0x00, 0x08, 0x08, 0x00, 0x00, 0x04, 0x04, 0x00, 65 0x00, 0x02, 0x02, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 66 }}, 67 {16, // BackwardDiagonal 68 16, 69 { 70 0x01, 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00, 71 0x00, 0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20, 72 0x00, 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x01, 73 0x01, 0x00, 0x00, 0x02, 0x02, 0x00, 0x00, 0x04, 0x04, 0x00, 0x00, 74 0x08, 0x08, 0x00, 0x00, 0x10, 0x10, 0x00, 0x00, 0x20, 0x20, 0x00, 75 0x00, 0x40, 0x40, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 76 }}, 77 {16, // Cross 78 16, 79 { 80 0xff, 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 81 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 82 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0xff, 83 0xff, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 84 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 85 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x00, 0x00, 86 }}, 87 {16, // DiagonalCross 88 16, 89 { 90 0x81, 0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00, 91 0x00, 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24, 92 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00, 0x81, 93 0x81, 0x00, 0x00, 0x42, 0x42, 0x00, 0x00, 0x24, 0x24, 0x00, 0x00, 94 0x18, 0x18, 0x00, 0x00, 0x18, 0x18, 0x00, 0x00, 0x24, 0x24, 0x00, 95 0x00, 0x42, 0x42, 0x00, 0x00, 0x81, 0x81, 0x00, 0x00, 96 }}, 97}; 98 99} // namespace 100 101CFX_Graphics::CFX_Graphics(CFX_RenderDevice* renderDevice) 102 : m_type(FX_CONTEXT_None), m_renderDevice(renderDevice) { 103 if (!renderDevice) 104 return; 105 m_type = FX_CONTEXT_Device; 106} 107 108CFX_Graphics::~CFX_Graphics() {} 109 110void CFX_Graphics::SaveGraphState() { 111 if (m_type != FX_CONTEXT_Device || !m_renderDevice) 112 return; 113 114 m_renderDevice->SaveState(); 115 m_infoStack.push_back(pdfium::MakeUnique<TInfo>(m_info)); 116} 117 118void CFX_Graphics::RestoreGraphState() { 119 if (m_type != FX_CONTEXT_Device || !m_renderDevice) 120 return; 121 122 m_renderDevice->RestoreState(false); 123 if (m_infoStack.empty() || !m_infoStack.back()) 124 return; 125 126 m_info = *m_infoStack.back(); 127 m_infoStack.pop_back(); 128 return; 129} 130 131void CFX_Graphics::SetLineCap(CFX_GraphStateData::LineCap lineCap) { 132 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 133 m_info.graphState.m_LineCap = lineCap; 134 } 135} 136 137void CFX_Graphics::SetLineDash(FX_FLOAT dashPhase, 138 FX_FLOAT* dashArray, 139 int32_t dashCount) { 140 if (dashCount > 0 && !dashArray) 141 return; 142 143 dashCount = dashCount < 0 ? 0 : dashCount; 144 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 145 FX_FLOAT scale = 1.0; 146 if (m_info.isActOnDash) { 147 scale = m_info.graphState.m_LineWidth; 148 } 149 m_info.graphState.m_DashPhase = dashPhase; 150 m_info.graphState.SetDashCount(dashCount); 151 for (int32_t i = 0; i < dashCount; i++) { 152 m_info.graphState.m_DashArray[i] = dashArray[i] * scale; 153 } 154 } 155} 156 157void CFX_Graphics::SetLineDash(FX_DashStyle dashStyle) { 158 if (m_type == FX_CONTEXT_Device && m_renderDevice) 159 RenderDeviceSetLineDash(dashStyle); 160} 161 162void CFX_Graphics::SetLineWidth(FX_FLOAT lineWidth, bool isActOnDash) { 163 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 164 m_info.graphState.m_LineWidth = lineWidth; 165 m_info.isActOnDash = isActOnDash; 166 } 167} 168 169void CFX_Graphics::SetStrokeColor(CFX_Color* color) { 170 if (!color) 171 return; 172 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 173 m_info.strokeColor = color; 174 } 175} 176 177void CFX_Graphics::SetFillColor(CFX_Color* color) { 178 if (!color) 179 return; 180 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 181 m_info.fillColor = color; 182 } 183} 184 185void CFX_Graphics::StrokePath(CFX_Path* path, CFX_Matrix* matrix) { 186 if (!path) 187 return; 188 if (m_type == FX_CONTEXT_Device && m_renderDevice) 189 RenderDeviceStrokePath(path, matrix); 190} 191 192void CFX_Graphics::FillPath(CFX_Path* path, 193 FX_FillMode fillMode, 194 CFX_Matrix* matrix) { 195 if (!path) 196 return; 197 if (m_type == FX_CONTEXT_Device && m_renderDevice) 198 RenderDeviceFillPath(path, fillMode, matrix); 199} 200 201void CFX_Graphics::StretchImage(CFX_DIBSource* source, 202 const CFX_RectF& rect, 203 CFX_Matrix* matrix) { 204 if (!source) 205 return; 206 if (m_type == FX_CONTEXT_Device && m_renderDevice) 207 RenderDeviceStretchImage(source, rect, matrix); 208} 209 210void CFX_Graphics::ConcatMatrix(const CFX_Matrix* matrix) { 211 if (!matrix) 212 return; 213 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 214 m_info.CTM.Concat(*matrix); 215 } 216} 217 218CFX_Matrix* CFX_Graphics::GetMatrix() { 219 if (m_type == FX_CONTEXT_Device && m_renderDevice) 220 return &m_info.CTM; 221 return nullptr; 222} 223 224CFX_RectF CFX_Graphics::GetClipRect() const { 225 if (m_type != FX_CONTEXT_Device || !m_renderDevice) 226 return CFX_RectF(); 227 228 FX_RECT r = m_renderDevice->GetClipBox(); 229 return CFX_Rect(r.left, r.top, r.Width(), r.Height()).As<FX_FLOAT>(); 230} 231 232void CFX_Graphics::SetClipRect(const CFX_RectF& rect) { 233 if (m_type == FX_CONTEXT_Device && m_renderDevice) { 234 m_renderDevice->SetClip_Rect( 235 FX_RECT(FXSYS_round(rect.left), FXSYS_round(rect.top), 236 FXSYS_round(rect.right()), FXSYS_round(rect.bottom()))); 237 } 238} 239 240CFX_RenderDevice* CFX_Graphics::GetRenderDevice() { 241 return m_renderDevice; 242} 243 244void CFX_Graphics::RenderDeviceSetLineDash(FX_DashStyle dashStyle) { 245 switch (dashStyle) { 246 case FX_DASHSTYLE_Solid: { 247 m_info.graphState.SetDashCount(0); 248 return; 249 } 250 case FX_DASHSTYLE_Dash: { 251 FX_FLOAT dashArray[] = {3, 1}; 252 SetLineDash(0, dashArray, 2); 253 return; 254 } 255 case FX_DASHSTYLE_Dot: { 256 FX_FLOAT dashArray[] = {1, 1}; 257 SetLineDash(0, dashArray, 2); 258 return; 259 } 260 case FX_DASHSTYLE_DashDot: { 261 FX_FLOAT dashArray[] = {3, 1, 1, 1}; 262 SetLineDash(0, dashArray, 4); 263 return; 264 } 265 case FX_DASHSTYLE_DashDotDot: { 266 FX_FLOAT dashArray[] = {4, 1, 2, 1, 2, 1}; 267 SetLineDash(0, dashArray, 6); 268 return; 269 } 270 default: 271 return; 272 } 273} 274 275void CFX_Graphics::RenderDeviceStrokePath(CFX_Path* path, CFX_Matrix* matrix) { 276 if (!m_info.strokeColor) 277 return; 278 CFX_Matrix m(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d, 279 m_info.CTM.e, m_info.CTM.f); 280 if (matrix) { 281 m.Concat(*matrix); 282 } 283 switch (m_info.strokeColor->m_type) { 284 case FX_COLOR_Solid: { 285 m_renderDevice->DrawPath(path->GetPathData(), &m, &m_info.graphState, 0x0, 286 m_info.strokeColor->m_info.argb, 0); 287 return; 288 } 289 default: 290 return; 291 } 292} 293 294void CFX_Graphics::RenderDeviceFillPath(CFX_Path* path, 295 FX_FillMode fillMode, 296 CFX_Matrix* matrix) { 297 if (!m_info.fillColor) 298 return; 299 CFX_Matrix m(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d, 300 m_info.CTM.e, m_info.CTM.f); 301 if (matrix) { 302 m.Concat(*matrix); 303 } 304 switch (m_info.fillColor->m_type) { 305 case FX_COLOR_Solid: { 306 m_renderDevice->DrawPath(path->GetPathData(), &m, &m_info.graphState, 307 m_info.fillColor->m_info.argb, 0x0, fillMode); 308 return; 309 } 310 case FX_COLOR_Pattern: 311 FillPathWithPattern(path, fillMode, &m); 312 return; 313 case FX_COLOR_Shading: 314 FillPathWithShading(path, fillMode, &m); 315 return; 316 default: 317 return; 318 } 319} 320 321void CFX_Graphics::RenderDeviceStretchImage(CFX_DIBSource* source, 322 const CFX_RectF& rect, 323 CFX_Matrix* matrix) { 324 CFX_Matrix m1(m_info.CTM.a, m_info.CTM.b, m_info.CTM.c, m_info.CTM.d, 325 m_info.CTM.e, m_info.CTM.f); 326 if (matrix) { 327 m1.Concat(*matrix); 328 } 329 std::unique_ptr<CFX_DIBitmap> bmp1 = 330 source->StretchTo((int32_t)rect.Width(), (int32_t)rect.Height()); 331 CFX_Matrix m2(rect.Width(), 0.0, 0.0, rect.Height(), rect.left, rect.top); 332 m2.Concat(m1); 333 334 int32_t left; 335 int32_t top; 336 std::unique_ptr<CFX_DIBitmap> bmp2 = bmp1->FlipImage(false, true); 337 std::unique_ptr<CFX_DIBitmap> bmp3 = bmp2->TransformTo(&m2, left, top); 338 CFX_RectF r = GetClipRect(); 339 CFX_DIBitmap* bitmap = m_renderDevice->GetBitmap(); 340 bitmap->CompositeBitmap(FXSYS_round(r.left), FXSYS_round(r.top), 341 FXSYS_round(r.Width()), FXSYS_round(r.Height()), 342 bmp3.get(), FXSYS_round(r.left - left), 343 FXSYS_round(r.top - top)); 344} 345 346void CFX_Graphics::FillPathWithPattern(CFX_Path* path, 347 FX_FillMode fillMode, 348 CFX_Matrix* matrix) { 349 CFX_Pattern* pattern = m_info.fillColor->m_info.pattern; 350 CFX_DIBitmap* bitmap = m_renderDevice->GetBitmap(); 351 int32_t width = bitmap->GetWidth(); 352 int32_t height = bitmap->GetHeight(); 353 CFX_DIBitmap bmp; 354 bmp.Create(width, height, FXDIB_Argb); 355 m_renderDevice->GetDIBits(&bmp, 0, 0); 356 357 FX_HatchStyle hatchStyle = m_info.fillColor->m_info.pattern->m_hatchStyle; 358 const FX_HATCHDATA& data = hatchBitmapData[static_cast<int>(hatchStyle)]; 359 360 CFX_DIBitmap mask; 361 mask.Create(data.width, data.height, FXDIB_1bppMask); 362 FXSYS_memcpy(mask.GetBuffer(), data.maskBits, mask.GetPitch() * data.height); 363 CFX_FloatRect rectf = path->GetPathData()->GetBoundingBox(); 364 if (matrix) 365 matrix->TransformRect(rectf); 366 367 FX_RECT rect(FXSYS_round(rectf.left), FXSYS_round(rectf.top), 368 FXSYS_round(rectf.right), FXSYS_round(rectf.bottom)); 369 CFX_FxgeDevice device; 370 device.Attach(&bmp, false, nullptr, false); 371 device.FillRect(&rect, m_info.fillColor->m_info.pattern->m_backArgb); 372 for (int32_t j = rect.bottom; j < rect.top; j += mask.GetHeight()) { 373 for (int32_t i = rect.left; i < rect.right; i += mask.GetWidth()) { 374 device.SetBitMask(&mask, i, j, 375 m_info.fillColor->m_info.pattern->m_foreArgb); 376 } 377 } 378 379 m_renderDevice->SaveState(); 380 m_renderDevice->SetClip_PathFill(path->GetPathData(), matrix, fillMode); 381 SetDIBitsWithMatrix(&bmp, &pattern->m_matrix); 382 m_renderDevice->RestoreState(false); 383} 384 385void CFX_Graphics::FillPathWithShading(CFX_Path* path, 386 FX_FillMode fillMode, 387 CFX_Matrix* matrix) { 388 CFX_DIBitmap* bitmap = m_renderDevice->GetBitmap(); 389 int32_t width = bitmap->GetWidth(); 390 int32_t height = bitmap->GetHeight(); 391 FX_FLOAT start_x = m_info.fillColor->m_shading->m_beginPoint.x; 392 FX_FLOAT start_y = m_info.fillColor->m_shading->m_beginPoint.y; 393 FX_FLOAT end_x = m_info.fillColor->m_shading->m_endPoint.x; 394 FX_FLOAT end_y = m_info.fillColor->m_shading->m_endPoint.y; 395 CFX_DIBitmap bmp; 396 bmp.Create(width, height, FXDIB_Argb); 397 m_renderDevice->GetDIBits(&bmp, 0, 0); 398 int32_t pitch = bmp.GetPitch(); 399 bool result = false; 400 switch (m_info.fillColor->m_shading->m_type) { 401 case FX_SHADING_Axial: { 402 FX_FLOAT x_span = end_x - start_x; 403 FX_FLOAT y_span = end_y - start_y; 404 FX_FLOAT axis_len_square = (x_span * x_span) + (y_span * y_span); 405 for (int32_t row = 0; row < height; row++) { 406 uint32_t* dib_buf = (uint32_t*)(bmp.GetBuffer() + row * pitch); 407 for (int32_t column = 0; column < width; column++) { 408 FX_FLOAT x = (FX_FLOAT)(column); 409 FX_FLOAT y = (FX_FLOAT)(row); 410 FX_FLOAT scale = 411 (((x - start_x) * x_span) + ((y - start_y) * y_span)) / 412 axis_len_square; 413 if (scale < 0) { 414 if (!m_info.fillColor->m_shading->m_isExtendedBegin) { 415 continue; 416 } 417 scale = 0; 418 } else if (scale > 1.0f) { 419 if (!m_info.fillColor->m_shading->m_isExtendedEnd) { 420 continue; 421 } 422 scale = 1.0f; 423 } 424 int32_t index = (int32_t)(scale * (FX_SHADING_Steps - 1)); 425 dib_buf[column] = m_info.fillColor->m_shading->m_argbArray[index]; 426 } 427 } 428 result = true; 429 break; 430 } 431 case FX_SHADING_Radial: { 432 FX_FLOAT start_r = m_info.fillColor->m_shading->m_beginRadius; 433 FX_FLOAT end_r = m_info.fillColor->m_shading->m_endRadius; 434 FX_FLOAT a = ((start_x - end_x) * (start_x - end_x)) + 435 ((start_y - end_y) * (start_y - end_y)) - 436 ((start_r - end_r) * (start_r - end_r)); 437 for (int32_t row = 0; row < height; row++) { 438 uint32_t* dib_buf = (uint32_t*)(bmp.GetBuffer() + row * pitch); 439 for (int32_t column = 0; column < width; column++) { 440 FX_FLOAT x = (FX_FLOAT)(column); 441 FX_FLOAT y = (FX_FLOAT)(row); 442 FX_FLOAT b = -2 * (((x - start_x) * (end_x - start_x)) + 443 ((y - start_y) * (end_y - start_y)) + 444 (start_r * (end_r - start_r))); 445 FX_FLOAT c = ((x - start_x) * (x - start_x)) + 446 ((y - start_y) * (y - start_y)) - (start_r * start_r); 447 FX_FLOAT s; 448 if (a == 0) { 449 s = -c / b; 450 } else { 451 FX_FLOAT b2_4ac = (b * b) - 4 * (a * c); 452 if (b2_4ac < 0) { 453 continue; 454 } 455 FX_FLOAT root = (FXSYS_sqrt(b2_4ac)); 456 FX_FLOAT s1, s2; 457 if (a > 0) { 458 s1 = (-b - root) / (2 * a); 459 s2 = (-b + root) / (2 * a); 460 } else { 461 s2 = (-b - root) / (2 * a); 462 s1 = (-b + root) / (2 * a); 463 } 464 if (s2 <= 1.0f || m_info.fillColor->m_shading->m_isExtendedEnd) { 465 s = (s2); 466 } else { 467 s = (s1); 468 } 469 if ((start_r) + s * (end_r - start_r) < 0) { 470 continue; 471 } 472 } 473 if (s < 0) { 474 if (!m_info.fillColor->m_shading->m_isExtendedBegin) { 475 continue; 476 } 477 s = 0; 478 } 479 if (s > 1.0f) { 480 if (!m_info.fillColor->m_shading->m_isExtendedEnd) { 481 continue; 482 } 483 s = 1.0f; 484 } 485 int index = (int32_t)(s * (FX_SHADING_Steps - 1)); 486 dib_buf[column] = m_info.fillColor->m_shading->m_argbArray[index]; 487 } 488 } 489 result = true; 490 break; 491 } 492 default: { 493 result = false; 494 break; 495 } 496 } 497 if (result) { 498 m_renderDevice->SaveState(); 499 m_renderDevice->SetClip_PathFill(path->GetPathData(), matrix, fillMode); 500 SetDIBitsWithMatrix(&bmp, matrix); 501 m_renderDevice->RestoreState(false); 502 } 503} 504 505void CFX_Graphics::SetDIBitsWithMatrix(CFX_DIBSource* source, 506 CFX_Matrix* matrix) { 507 if (matrix->IsIdentity()) { 508 m_renderDevice->SetDIBits(source, 0, 0); 509 } else { 510 CFX_Matrix m((FX_FLOAT)source->GetWidth(), 0, 0, 511 (FX_FLOAT)source->GetHeight(), 0, 0); 512 m.Concat(*matrix); 513 int32_t left; 514 int32_t top; 515 std::unique_ptr<CFX_DIBitmap> bmp1 = source->FlipImage(false, true); 516 std::unique_ptr<CFX_DIBitmap> bmp2 = bmp1->TransformTo(&m, left, top); 517 m_renderDevice->SetDIBits(bmp2.get(), left, top); 518 } 519} 520 521CFX_Graphics::TInfo::TInfo() 522 : isActOnDash(false), strokeColor(nullptr), fillColor(nullptr) {} 523 524CFX_Graphics::TInfo::TInfo(const TInfo& info) 525 : graphState(info.graphState), 526 CTM(info.CTM), 527 isActOnDash(info.isActOnDash), 528 strokeColor(info.strokeColor), 529 fillColor(info.fillColor) {} 530 531CFX_Graphics::TInfo& CFX_Graphics::TInfo::operator=(const TInfo& other) { 532 graphState.Copy(other.graphState); 533 CTM = other.CTM; 534 isActOnDash = other.isActOnDash; 535 strokeColor = other.strokeColor; 536 fillColor = other.fillColor; 537 return *this; 538} 539