30 : thickness (strokeThickness), jointStyle (mitered), endStyle (butt)
35 : thickness (strokeThickness), jointStyle (joint), endStyle (
end)
40 : thickness (other.thickness),
41 jointStyle (other.jointStyle),
42 endStyle (other.endStyle)
48 thickness = other.thickness;
49 jointStyle = other.jointStyle;
50 endStyle = other.endStyle;
60 const auto tie = [] (
const PathStrokeType& p) {
return std::tie (p.thickness, p.jointStyle, p.endStyle); };
61 return tie (*
this) == tie (other);
66 return ! operator== (other);
70namespace PathStrokeHelpers
75 float distanceBeyondLine1EndSquared;
80 const float x2,
const float y2,
81 const float x3,
const float y3,
82 const float x4,
const float y4)
86 const auto dx1 = x2 - x1;
87 const auto dy1 = y2 -
y1;
88 const auto dx2 = x4 - x3;
89 const auto dy2 = y4 - y3;
90 const auto divisor = dx1 * dy2 - dx2 * dy1;
99 const auto along = (
y1 - y3) / dy2;
100 const auto intersectionX = x3 + along * dx2;
101 const auto intersectionY =
y1;
103 const auto distance =
square (intersectionX - x2);
104 const auto distanceBeyondLine1EndSquared = (x2 > x1) == (intersectionX < x2)
108 return { { intersectionX, intersectionY },
109 distanceBeyondLine1EndSquared,
110 along >= 0 && along <= 1.0f };
115 const auto along = (y3 -
y1) / dy1;
116 const auto intersectionX = x1 + along * dx1;
117 const auto intersectionY = y3;
120 const auto distanceBeyondLine1EndSquared = along < 1.0f ? -
distance :
distance;
122 return { { intersectionX, intersectionY },
123 distanceBeyondLine1EndSquared,
124 along >= 0 && along <= 1.0f };
129 const auto along = (x1 - x3) / dx2;
130 const auto intersectionX = x1;
131 const auto intersectionY = y3 + along * dy2;
134 const auto distanceBeyondLine1EndSquared = (y2 >
y1) == (intersectionY < y2)
138 return { { intersectionX, intersectionY },
139 distanceBeyondLine1EndSquared,
140 along >= 0 && along <= 1.0f };
145 const auto along = (x3 - x1) / dx1;
146 const auto intersectionX = x3;
147 const auto intersectionY =
y1 + along * dy1;
150 const auto distanceBeyondLine1EndSquared = along < 1.0f ? -
distance :
distance;
152 return { { intersectionX, intersectionY },
153 distanceBeyondLine1EndSquared,
154 along >= 0 && along <= 1.0f };
158 const auto intersectionX = 0.5f * (x2 + x3);
159 const auto intersectionY = 0.5f * (y2 + y3);
161 const auto distanceBeyondLine1EndSquared = 0.0f;
163 return { { intersectionX, intersectionY },
164 distanceBeyondLine1EndSquared,
168 const auto along = ((
y1 - y3) * dx2 - (x1 - x3) * dy2) / divisor;
170 const auto intersectionX = x1 + along * dx1;
171 const auto intersectionY =
y1 + along * dy1;
173 if (along >= 0 && along <= 1.0f)
175 const auto along2 = ((
y1 - y3) * dx1 - (x1 - x3) * dy1) / divisor;
177 if (along2 >= 0 && along2 <= 1.0f)
179 return { { intersectionX, intersectionY },
185 const auto distance =
square (along - 1.0f) * (dx1 * dx1 + dy1 * dy1);
186 const auto distanceBeyondLine1EndSquared = along < 1.0f ? -
distance :
distance;
188 return { { intersectionX, intersectionY },
189 distanceBeyondLine1EndSquared,
193 return { Point { x2, y2 }, 0.0f,
true };
196 static void addEdgeAndJoint (Path& destPath,
198 const float maxMiterExtensionSquared,
const float width,
199 const float x1,
const float y1,
200 const float x2,
const float y2,
201 const float x3,
const float y3,
202 const float x4,
const float y4,
203 const float midX,
const float midY)
209 destPath.lineTo (x2, y2);
210 destPath.lineTo (x3, y3);
214 const auto intersection = lineIntersection (x1, y1, x2, y2, x3, y3, x4, y4);
217 if (intersection.intersects)
219 destPath.lineTo (intersection.point);
225 if (0.0f < intersection.distanceBeyondLine1EndSquared
226 && intersection.distanceBeyondLine1EndSquared < maxMiterExtensionSquared)
228 destPath.lineTo (intersection.point);
233 destPath.lineTo (x2, y2);
234 destPath.lineTo (x3, y3);
240 float angle1 =
std::atan2 (x2 - midX, y2 - midY);
241 float angle2 =
std::atan2 (x3 - midX, y3 - midY);
242 const float angleIncrement = 0.1f;
244 destPath.lineTo (x2, y2);
246 if (std::abs (angle1 - angle2) > angleIncrement)
256 angle1 -= angleIncrement;
257 while (angle1 > angle2)
259 destPath.lineTo (midX + width *
std::sin (angle1),
262 angle1 -= angleIncrement;
272 angle1 += angleIncrement;
273 while (angle1 < angle2)
275 destPath.lineTo (midX + width *
std::sin (angle1),
278 angle1 += angleIncrement;
283 destPath.lineTo (x3, y3);
289 static void addLineEnd (Path& destPath,
291 const float x1,
const float y1,
292 const float x2,
const float y2,
297 destPath.lineTo (x2, y2);
301 float offx1, offy1, offx2, offy2;
314 auto offset = width / len;
327 destPath.lineTo (offx1, offy1);
328 destPath.lineTo (offx2, offy2);
329 destPath.lineTo (x2, y2);
334 auto midx = (offx1 + offx2) * 0.5f;
335 auto midy = (offy1 + offy2) * 0.5f;
337 destPath.cubicTo (x1 + (offx1 - x1) * 0.55f,
y1 + (offy1 -
y1) * 0.55f,
338 offx1 + (midx - offx1) * 0.45f, offy1 + (midy - offy1) * 0.45f,
341 destPath.cubicTo (midx + (offx2 - midx) * 0.55f, midy + (offy2 - midy) * 0.55f,
342 offx2 + (x2 - offx2) * 0.45f, offy2 + (y2 - offy2) * 0.45f,
350 float startWidth, startLength;
351 float endWidth, endLength;
354 static void addArrowhead (
Path& destPath,
355 const float x1,
const float y1,
356 const float x2,
const float y2,
357 const float tipX,
const float tipY,
359 const float arrowheadWidth)
362 destPath.
lineTo (line.getPointAlongLine (-(arrowheadWidth / 2.0f - width), 0));
363 destPath.
lineTo (tipX, tipY);
364 destPath.
lineTo (line.getPointAlongLine (arrowheadWidth - (arrowheadWidth / 2.0f - width), 0));
370 float x1,
y1, x2, y2;
371 float lx1, ly1, lx2, ly2;
372 float rx1, ry1, rx2, ry2;
375 static void shortenSubPath (
Array<LineSection>& subPath,
float amountAtStart,
float amountAtEnd)
377 while (amountAtEnd > 0 && subPath.
size() > 0)
380 auto dx = l.rx2 - l.rx1;
381 auto dy = l.ry2 - l.ry1;
384 if (len <= amountAtEnd && subPath.
size() > 1)
394 auto prop =
jmin (0.9999f, amountAtEnd / len);
405 while (amountAtStart > 0 && subPath.
size() > 0)
408 auto dx = l.rx2 - l.rx1;
409 auto dy = l.ry2 - l.ry1;
412 if (len <= amountAtStart && subPath.
size() > 1)
418 amountAtStart -= len;
422 auto prop =
jmin (0.9999f, amountAtStart / len);
434 static void addSubPath (Path& destPath, Array<LineSection>& subPath,
435 const bool isClosed,
const float width,
const float maxMiterExtensionSquared,
437 const Arrowhead*
const arrowhead)
441 if (arrowhead !=
nullptr)
442 shortenSubPath (subPath, arrowhead->startLength, arrowhead->endLength);
444 auto& firstLine = subPath.getReference (0);
446 auto lastX1 = firstLine.lx1;
447 auto lastY1 = firstLine.ly1;
448 auto lastX2 = firstLine.lx2;
449 auto lastY2 = firstLine.ly2;
453 destPath.startNewSubPath (lastX1, lastY1);
457 destPath.startNewSubPath (firstLine.rx2, firstLine.ry2);
459 if (arrowhead !=
nullptr && arrowhead->startWidth > 0.0f)
460 addArrowhead (destPath, firstLine.rx2, firstLine.ry2, lastX1, lastY1, firstLine.x1, firstLine.y1,
461 width, arrowhead->startWidth);
463 addLineEnd (destPath, endStyle, firstLine.rx2, firstLine.ry2, lastX1, lastY1, width);
466 for (
int i = 1; i < subPath.size(); ++i)
468 const LineSection& l = subPath.getReference (i);
470 addEdgeAndJoint (destPath, jointStyle,
471 maxMiterExtensionSquared, width,
472 lastX1, lastY1, lastX2, lastY2,
473 l.lx1, l.ly1, l.lx2, l.ly2,
482 auto& lastLine = subPath.getReference (subPath.size() - 1);
486 auto& l = subPath.getReference (0);
488 addEdgeAndJoint (destPath, jointStyle,
489 maxMiterExtensionSquared, width,
490 lastX1, lastY1, lastX2, lastY2,
491 l.lx1, l.ly1, l.lx2, l.ly2,
494 destPath.closeSubPath();
495 destPath.startNewSubPath (lastLine.rx1, lastLine.ry1);
499 destPath.lineTo (lastX2, lastY2);
501 if (arrowhead !=
nullptr && arrowhead->endWidth > 0.0f)
502 addArrowhead (destPath, lastX2, lastY2, lastLine.rx1, lastLine.ry1, lastLine.x2, lastLine.y2,
503 width, arrowhead->endWidth);
505 addLineEnd (destPath, endStyle, lastX2, lastY2, lastLine.rx1, lastLine.ry1, width);
508 lastX1 = lastLine.rx1;
509 lastY1 = lastLine.ry1;
510 lastX2 = lastLine.rx2;
511 lastY2 = lastLine.ry2;
513 for (
int i = subPath.size() - 1; --i >= 0;)
515 auto& l = subPath.getReference (i);
517 addEdgeAndJoint (destPath, jointStyle,
518 maxMiterExtensionSquared, width,
519 lastX1, lastY1, lastX2, lastY2,
520 l.rx1, l.ry1, l.rx2, l.ry2,
531 addEdgeAndJoint (destPath, jointStyle,
532 maxMiterExtensionSquared, width,
533 lastX1, lastY1, lastX2, lastY2,
534 lastLine.rx1, lastLine.ry1, lastLine.rx2, lastLine.ry2,
535 lastLine.x2, lastLine.y2);
540 destPath.lineTo (lastX2, lastY2);
543 destPath.closeSubPath();
548 Path& destPath,
const Path& source,
549 const AffineTransform& transform,
550 const float extraAccuracy,
const Arrowhead*
const arrowhead)
560 const Path* sourcePath = &source;
563 if (sourcePath == &destPath)
565 destPath.swapWithPath (temp);
573 destPath.setUsingNonZeroWinding (
true);
575 const float maxMiterExtensionSquared = 9.0f * thickness * thickness;
576 const float width = 0.5f * thickness;
580 PathFlatteningIterator it (*sourcePath, transform, Path::defaultToleranceForMeasurement / extraAccuracy);
582 Array<LineSection> subPath;
583 subPath.ensureStorageAllocated (512);
588 const float minSegmentLength = 0.0001f;
592 if (it.subPathIndex == 0)
594 if (subPath.size() > 0)
596 addSubPath (destPath, subPath,
false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
597 subPath.clearQuick();
607 float dx = l.x2 - l.x1;
608 float dy = l.y2 - l.y1;
610 auto hypotSquared = dx * dx + dy * dy;
612 if (it.closesSubPath || hypotSquared > minSegmentLength || it.isLastInSubpath())
618 l.rx1 = l.rx2 = l.lx1 = l.lx2 = l.x1;
619 l.ry1 = l.ry2 = l.ly1 = l.ly2 = l.y1;
623 auto offset = width / len;
640 if (it.closesSubPath)
642 addSubPath (destPath, subPath,
true, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
643 subPath.clearQuick();
653 if (subPath.size() > 0)
654 addSubPath (destPath, subPath,
false, width, maxMiterExtensionSquared, jointStyle, endStyle, arrowhead);
661 PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle, destPath, sourcePath,
662 transform, extraAccuracy,
nullptr);
666 const Path& sourcePath,
667 const float* dashLengths,
670 float extraAccuracy)
const
682 float pos = 0.0f, lineLen = 0.0f, lineEndPos = 0.0f;
683 float dx = 0.0f, dy = 0.0f;
687 const bool isSolid = ((dashNum & 1) == 0);
688 const float dashLen = dashLengths [dashNum++ % numDashLengths];
696 while (pos > lineEndPos)
700 if (isSolid && ! first)
707 if (isSolid && ! first)
715 lineEndPos += lineLen;
719 const float alpha = (pos - (lineEndPos - lineLen)) / lineLen;
722 newDestPath.
lineTo (it.
x1 + dx * alpha,
731 const Path& sourcePath,
732 const float arrowheadStartWidth,
const float arrowheadStartLength,
733 const float arrowheadEndWidth,
const float arrowheadEndLength,
735 const float extraAccuracy)
const
738 head.startWidth = arrowheadStartWidth;
739 head.startLength = arrowheadStartLength;
740 head.endWidth = arrowheadEndWidth;
741 head.endLength = arrowheadEndLength;
743 PathStrokeHelpers::createStroke (thickness, jointStyle, endStyle,
744 destPath, sourcePath, transform, extraAccuracy, &head);
Holds a resizable array of primitive or copy-by-value objects.
void removeLast(int howManyToRemove=1)
Removes the last n elements from the array.
int size() const noexcept
Returns the current number of elements in the array.
void remove(int indexToRemove)
Removes an element from the array.
ElementType & getReference(int index) noexcept
Returns a direct reference to one of the elements in the array, without checking the index passed in.
Flattens a Path object into a series of straight-line sections.
bool closesSubPath
Indicates whether the current line segment is closing a sub-path.
bool next()
Fetches the next line segment from the path.
float y2
The y position of the end of the current line segment.
float x2
The x position of the end of the current line segment.
float y1
The y position of the start of the current line segment.
float x1
The x position of the start of the current line segment.
Describes a type of stroke used to render a solid outline along a path.
bool operator==(const PathStrokeType &) const noexcept
Compares the stroke thickness, joint and end styles of two stroke types.
JointStyle
The type of shape to use for the corners between two adjacent line segments.
@ beveled
Indicates that corners should be drawn with a line flattening their outside edge.
@ mitered
Indicates that corners should be drawn with sharp joints.
void createStrokedPath(Path &destPath, const Path &sourcePath, const AffineTransform &transform=AffineTransform(), float extraAccuracy=1.0f) const
Applies this stroke type to a path and returns the resultant stroke as another Path.
PathStrokeType(float strokeThickness) noexcept
Creates a stroke type with a given line-width, and default joint/end styles.
EndCapStyle
The type shape to use for the ends of lines.
@ square
Ends of lines are flat, but stick out beyond the end point for half the thickness of the stroke.
@ butt
Ends of lines are flat and don't extend beyond the end point.
void createDashedStroke(Path &destPath, const Path &sourcePath, const float *dashLengths, int numDashLengths, const AffineTransform &transform=AffineTransform(), float extraAccuracy=1.0f) const
Applies this stroke type to a path, creating a dashed line.
bool operator!=(const PathStrokeType &) const noexcept
Compares the stroke thickness, joint and end styles of two stroke types.
~PathStrokeType() noexcept
Destructor.
PathStrokeType & operator=(const PathStrokeType &) noexcept
Copies another stroke onto this one.
void createStrokeWithArrowheads(Path &destPath, const Path &sourcePath, float arrowheadStartWidth, float arrowheadStartLength, float arrowheadEndWidth, float arrowheadEndLength, const AffineTransform &transform=AffineTransform(), float extraAccuracy=1.0f) const
Applies this stroke type to a path and returns the resultant stroke as another Path.
A path is a sequence of lines and curves that may either form a closed shape or be open-ended.
void startNewSubPath(float startX, float startY)
Begins a new subpath with a given starting position.
void lineTo(float endX, float endY)
Adds a line from the shape's last position to a new end-point.
A pair of (x, y) coordinates.
constexpr bool approximatelyEqual(Type a, Type b, Tolerance< Type > tolerance=Tolerance< Type >{} .withAbsolute(std::numeric_limits< Type >::min()) .withRelative(std::numeric_limits< Type >::epsilon()))
Returns true if the two floating-point numbers are approximately equal.
constexpr Type jmin(Type a, Type b)
Returns the smaller of two values.
RangedDirectoryIterator end(const RangedDirectoryIterator &)
Returns a default-constructed sentinel value.
constexpr NumericType square(NumericType n) noexcept
Returns the square of its argument.
Type juce_hypot(Type a, Type b) noexcept
Using juce_hypot is easier than dealing with the different types of hypot function that are provided ...
static constexpr FloatType twoPi
A predefined value for 2 * Pi.
static constexpr FloatType pi
A predefined value for Pi.