Skip to content

Commit c60a9a4

Browse files
committed
Draw text using JUCE paths, calculate path intersections
1 parent eb9ce23 commit c60a9a4

File tree

5 files changed

+165
-4
lines changed

5 files changed

+165
-4
lines changed

Libraries/nanovg

Source/Components/DraggableNumber.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -903,6 +903,7 @@ struct DraggableListNumber final : public DraggableNumber {
903903
}
904904
return;
905905
}
906+
906907

907908
if (hoveredDecimal >= 0) {
908909
auto const highlightColour = outlineColour.withAlpha(isMouseButtonDown() ? 0.5f : 0.3f);

Source/NVGSurface.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,7 @@ class NVGImage {
379379
subImage.imageId = nvgCreateImageARGB(nvg, totalWidth, totalHeight, flags | NVG_IMAGE_PREMULTIPLIED, imageData.data);
380380
else if (image.isSingleChannel())
381381
subImage.imageId = nvgCreateImageAlpha(nvg, totalWidth, totalHeight, flags, imageData.data);
382-
382+
383383
deleteImage();
384384

385385
subImage.bounds = image.getBounds();

Source/Utility/NanoVGGraphicsContext.cpp

Lines changed: 160 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -296,6 +296,154 @@ void NanoVGGraphicsContext::setPath(juce::Path const& path, juce::AffineTransfor
296296
}
297297
}
298298

299+
std::vector<juce::Path> splitIntoSubPaths(const juce::Path& original)
300+
{
301+
std::vector<juce::Path> subPaths;
302+
juce::Path currentSubPath;
303+
304+
juce::Path::Iterator it(original);
305+
while (it.next()) {
306+
switch (it.elementType) {
307+
case juce::Path::Iterator::startNewSubPath:
308+
if (!currentSubPath.isEmpty()) {
309+
subPaths.push_back(currentSubPath);
310+
currentSubPath.clear();
311+
}
312+
currentSubPath.startNewSubPath(it.x1, it.y1);
313+
break;
314+
315+
case juce::Path::Iterator::lineTo:
316+
currentSubPath.lineTo(it.x1, it.y1);
317+
break;
318+
319+
case juce::Path::Iterator::quadraticTo:
320+
currentSubPath.quadraticTo(it.x1, it.y1, it.x2, it.y2);
321+
break;
322+
323+
case juce::Path::Iterator::cubicTo:
324+
currentSubPath.cubicTo(it.x1, it.y1, it.x2, it.y2, it.x3, it.y3);
325+
break;
326+
327+
case juce::Path::Iterator::closePath:
328+
currentSubPath.closeSubPath();
329+
break;
330+
}
331+
}
332+
333+
// Push the final path if it's not empty
334+
if (!currentSubPath.isEmpty())
335+
subPaths.push_back(currentSubPath);
336+
337+
return subPaths;
338+
}
339+
340+
bool linesIntersect(juce::Point<float> a1, juce::Point<float> a2,
341+
juce::Point<float> b1, juce::Point<float> b2)
342+
{
343+
auto cross = [](juce::Point<float> v1, juce::Point<float> v2) {
344+
return v1.x * v2.y - v1.y * v2.x;
345+
};
346+
347+
juce::Point<float> da = a2 - a1;
348+
juce::Point<float> db = b2 - b1;
349+
juce::Point<float> delta = b1 - a1;
350+
351+
float denom = cross(da, db);
352+
if (std::abs(denom) < 1e-6f)
353+
return false; // Lines are parallel
354+
355+
float t = cross(delta, db) / denom;
356+
float u = cross(delta, da) / denom;
357+
358+
return (t >= 0.0f && t <= 1.0f && u >= 0.0f && u <= 1.0f);
359+
}
360+
361+
void NanoVGGraphicsContext::setEvenOddPath(juce::Path const& path, juce::AffineTransform const& transform)
362+
{
363+
juce::Path p(path);
364+
p.applyTransform(transform);
365+
366+
nvgBeginPath(nvg);
367+
nvgPathWinding(nvg, NVG_SOLID);
368+
369+
auto subpaths = splitIntoSubPaths(p);
370+
371+
for (auto& path : subpaths) {
372+
int crossings = 0;
373+
juce::Point<float> testPoint = path.getPointAlongPath(0); // point on current path
374+
juce::Point<float> outsidePoint = path.getBounds().getTopLeft() - juce::Point<float>(10.0f, 10.0f); // outside point
375+
376+
for (auto& other : subpaths) {
377+
if (other == path)
378+
continue;
379+
380+
juce::Path::Iterator it(other);
381+
juce::Point<float> p0;
382+
383+
while (it.next()) {
384+
switch (it.elementType) {
385+
case juce::Path::Iterator::startNewSubPath:
386+
p0 = { it.x1, it.y1 };
387+
break;
388+
389+
case juce::Path::Iterator::lineTo: {
390+
juce::Point<float> p1 = { it.x1, it.y1 };
391+
if (linesIntersect(testPoint, outsidePoint, p0, p1))
392+
crossings++;
393+
p0 = p1;
394+
break;
395+
}
396+
397+
case juce::Path::Iterator::quadraticTo: {
398+
juce::Point<float> p1 = { it.x2, it.y2 }; // end point
399+
if (linesIntersect(testPoint, outsidePoint, p0, p1))
400+
crossings++;
401+
p0 = p1;
402+
break;
403+
}
404+
405+
case juce::Path::Iterator::cubicTo: {
406+
juce::Point<float> p1 = { it.x3, it.y3 }; // end point
407+
if (linesIntersect(testPoint, outsidePoint, p0, p1))
408+
crossings++;
409+
p0 = p1;
410+
break;
411+
}
412+
413+
case juce::Path::Iterator::closePath:
414+
break;
415+
}
416+
}
417+
}
418+
419+
juce::Path::Iterator i(path);
420+
while (i.next()) {
421+
switch (i.elementType) {
422+
case juce::Path::Iterator::startNewSubPath:
423+
nvgMoveTo(nvg, i.x1, i.y1);
424+
break;
425+
case juce::Path::Iterator::lineTo:
426+
nvgLineTo(nvg, i.x1, i.y1);
427+
break;
428+
case juce::Path::Iterator::quadraticTo:
429+
nvgQuadTo(nvg, i.x1, i.y1, i.x2, i.y2);
430+
break;
431+
case juce::Path::Iterator::cubicTo:
432+
nvgBezierTo(nvg, i.x1, i.y1, i.x2, i.y2, i.x3, i.y3);
433+
break;
434+
case juce::Path::Iterator::closePath:
435+
nvgClosePath(nvg);
436+
break;
437+
default:
438+
break;
439+
}
440+
}
441+
442+
nvgPathWinding(nvg, (crossings % 2 == 1) ? NVG_HOLE : NVG_SOLID);
443+
444+
}
445+
}
446+
299447
void NanoVGGraphicsContext::fillPath(juce::Path const& path, juce::AffineTransform const& transform)
300448
{
301449
setPath(path, transform);
@@ -449,12 +597,23 @@ void NanoVGGraphicsContext::drawGlyph(int const glyphNumber, juce::AffineTransfo
449597
utf8.write(wc);
450598
utf8.writeNull();
451599

600+
nvgSave(nvg);
601+
602+
juce::Path p;
603+
auto f = getFont();
604+
f.getTypefacePtr()->getOutlineForGlyph (glyphNumber, p);
605+
setEvenOddPath(p, juce::AffineTransform::scale (f.getHeight() * f.getHorizontalScale(), f.getHeight())
606+
.followedBy (transform));
607+
nvgFill(nvg);
608+
nvgRestore(nvg);
609+
610+
/*
452611
nvgSave(nvg);
453612
setFont(getFont());
454613
nvgTransform(nvg, transform.mat00, transform.mat10, transform.mat01, transform.mat11, transform.mat02, transform.mat12);
455614
nvgTextAlign(nvg, NVG_ALIGN_BASELINE | NVG_ALIGN_LEFT);
456615
nvgText(nvg, 0, 1, txt, &txt[1]);
457-
nvgRestore(nvg);
616+
nvgRestore(nvg);*/
458617
}
459618

460619
bool NanoVGGraphicsContext::drawTextLayout(juce::AttributedString const& str, juce::Rectangle<float> const& rect)

Source/Utility/NanoVGGraphicsContext.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@ class NanoVGGraphicsContext final : public juce::LowLevelGraphicsContext {
5151
void fillRectList(juce::RectangleList<float> const&) override;
5252

5353
void setPath(juce::Path const& path, juce::AffineTransform const& transform);
54-
54+
void setEvenOddPath(const juce::Path& path, const juce::AffineTransform& transform);
55+
5556
void strokePath(juce::Path const&, juce::PathStrokeType const&, juce::AffineTransform const&);
5657
void fillPath(juce::Path const&, juce::AffineTransform const&) override;
5758
void drawImage(juce::Image const&, juce::AffineTransform const&) override;

0 commit comments

Comments
 (0)