mirror of
https://github.com/RetroDECK/ES-DE.git
synced 2024-11-25 15:45:38 +00:00
767 lines
20 KiB
C++
767 lines
20 KiB
C++
#include "layoutcontext.h"
|
|
#include "parser.h"
|
|
|
|
#include "maskelement.h"
|
|
#include "clippathelement.h"
|
|
#include "paintelement.h"
|
|
#include "markerelement.h"
|
|
#include "geometryelement.h"
|
|
|
|
#include <cmath>
|
|
|
|
namespace lunasvg {
|
|
|
|
LayoutObject::LayoutObject(LayoutId id)
|
|
: id(id)
|
|
{
|
|
}
|
|
|
|
LayoutObject::~LayoutObject()
|
|
{
|
|
}
|
|
|
|
void LayoutObject::render(RenderState&) const
|
|
{
|
|
}
|
|
|
|
void LayoutObject::apply(RenderState&) const
|
|
{
|
|
}
|
|
|
|
Rect LayoutObject::map(const Rect&) const
|
|
{
|
|
return Rect::Invalid;
|
|
}
|
|
|
|
LayoutContainer::LayoutContainer(LayoutId id)
|
|
: LayoutObject(id)
|
|
{
|
|
}
|
|
|
|
const Rect& LayoutContainer::fillBoundingBox() const
|
|
{
|
|
if(m_fillBoundingBox.valid())
|
|
return m_fillBoundingBox;
|
|
|
|
for(const auto& child : children)
|
|
{
|
|
if(child->isHidden())
|
|
continue;
|
|
m_fillBoundingBox.unite(child->map(child->fillBoundingBox()));
|
|
}
|
|
|
|
return m_fillBoundingBox;
|
|
}
|
|
|
|
const Rect& LayoutContainer::strokeBoundingBox() const
|
|
{
|
|
if(m_strokeBoundingBox.valid())
|
|
return m_strokeBoundingBox;
|
|
|
|
for(const auto& child : children)
|
|
{
|
|
if(child->isHidden())
|
|
continue;
|
|
m_strokeBoundingBox.unite(child->map(child->strokeBoundingBox()));
|
|
}
|
|
|
|
return m_strokeBoundingBox;
|
|
}
|
|
|
|
LayoutObject* LayoutContainer::addChild(std::unique_ptr<LayoutObject> child)
|
|
{
|
|
children.push_back(std::move(child));
|
|
return &*children.back();
|
|
}
|
|
|
|
LayoutObject* LayoutContainer::addChildIfNotEmpty(std::unique_ptr<LayoutContainer> child)
|
|
{
|
|
if(child->children.empty())
|
|
return nullptr;
|
|
|
|
return addChild(std::move(child));
|
|
}
|
|
|
|
void LayoutContainer::renderChildren(RenderState& state) const
|
|
{
|
|
for(const auto& child : children)
|
|
child->render(state);
|
|
}
|
|
|
|
LayoutClipPath::LayoutClipPath()
|
|
: LayoutContainer(LayoutId::ClipPath)
|
|
{
|
|
}
|
|
|
|
void LayoutClipPath::apply(RenderState& state) const
|
|
{
|
|
RenderState newState(this, RenderMode::Clipping);
|
|
newState.canvas = Canvas::create(state.canvas->box());
|
|
newState.transform = transform * state.transform;
|
|
if(units == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
newState.transform.translate(box.x, box.y);
|
|
newState.transform.scale(box.w, box.h);
|
|
}
|
|
|
|
renderChildren(newState);
|
|
if(clipper) clipper->apply(newState);
|
|
state.canvas->blend(newState.canvas.get(), BlendMode::Dst_In, 1.0);
|
|
}
|
|
|
|
LayoutMask::LayoutMask()
|
|
: LayoutContainer(LayoutId::Mask)
|
|
{
|
|
}
|
|
|
|
void LayoutMask::apply(RenderState& state) const
|
|
{
|
|
Rect rect{x, y, width, height};
|
|
if(units == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
rect.x = rect.x * box.w + box.x;
|
|
rect.y = rect.y * box.h + box.y;
|
|
rect.w = rect.w * box.w;
|
|
rect.h = rect.h * box.h;
|
|
}
|
|
|
|
RenderState newState(this, state.mode());
|
|
newState.canvas = Canvas::create(state.canvas->box());
|
|
newState.transform = state.transform;
|
|
if(contentUnits == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
newState.transform.translate(box.x, box.y);
|
|
newState.transform.scale(box.w, box.h);
|
|
}
|
|
|
|
renderChildren(newState);
|
|
if(clipper) clipper->apply(newState);
|
|
if(masker) masker->apply(newState);
|
|
newState.canvas->mask(rect, state.transform);
|
|
newState.canvas->luminance();
|
|
state.canvas->blend(newState.canvas.get(), BlendMode::Dst_In, opacity);
|
|
}
|
|
|
|
LayoutSymbol::LayoutSymbol()
|
|
: LayoutContainer(LayoutId::Symbol)
|
|
{
|
|
}
|
|
|
|
void LayoutSymbol::render(RenderState& state) const
|
|
{
|
|
BlendInfo info{clipper, masker, opacity, clip};
|
|
RenderState newState(this, state.mode());
|
|
newState.transform = transform * state.transform;
|
|
newState.beginGroup(state, info);
|
|
renderChildren(newState);
|
|
newState.endGroup(state, info);
|
|
}
|
|
|
|
Rect LayoutSymbol::map(const Rect& rect) const
|
|
{
|
|
return transform.map(rect);
|
|
}
|
|
|
|
LayoutGroup::LayoutGroup()
|
|
: LayoutContainer(LayoutId::Group)
|
|
{
|
|
}
|
|
|
|
void LayoutGroup::render(RenderState& state) const
|
|
{
|
|
BlendInfo info{clipper, masker, opacity, Rect::Invalid};
|
|
RenderState newState(this, state.mode());
|
|
newState.transform = transform * state.transform;
|
|
newState.beginGroup(state, info);
|
|
renderChildren(newState);
|
|
newState.endGroup(state, info);
|
|
}
|
|
|
|
Rect LayoutGroup::map(const Rect& rect) const
|
|
{
|
|
return transform.map(rect);
|
|
}
|
|
|
|
LayoutMarker::LayoutMarker()
|
|
: LayoutContainer(LayoutId::Marker)
|
|
{
|
|
}
|
|
|
|
Transform LayoutMarker::markerTransform(const Point& origin, double angle, double strokeWidth) const
|
|
{
|
|
auto transform = Transform::translated(origin.x, origin.y);
|
|
if(orient.type() == MarkerOrient::Auto)
|
|
transform.rotate(angle);
|
|
else
|
|
transform.rotate(orient.value());
|
|
|
|
if(units == MarkerUnits::StrokeWidth)
|
|
transform.scale(strokeWidth, strokeWidth);
|
|
|
|
transform.translate(-refX, -refY);
|
|
return transform;
|
|
}
|
|
|
|
Rect LayoutMarker::markerBoundingBox(const Point& origin, double angle, double strokeWidth) const
|
|
{
|
|
auto box = transform.map(strokeBoundingBox());
|
|
auto transform = markerTransform(origin, angle, strokeWidth);
|
|
return transform.map(box);
|
|
}
|
|
|
|
void LayoutMarker::renderMarker(RenderState& state, const Point& origin, double angle, double strokeWidth) const
|
|
{
|
|
BlendInfo info{clipper, masker, opacity, clip};
|
|
RenderState newState(this, state.mode());
|
|
newState.transform = transform * markerTransform(origin, angle, strokeWidth) * state.transform;
|
|
newState.beginGroup(state, info);
|
|
renderChildren(newState);
|
|
newState.endGroup(state, info);
|
|
}
|
|
|
|
LayoutPattern::LayoutPattern()
|
|
: LayoutContainer(LayoutId::Pattern)
|
|
{
|
|
}
|
|
|
|
void LayoutPattern::apply(RenderState& state) const
|
|
{
|
|
Rect rect{x, y, width, height};
|
|
if(units == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
rect.x = rect.x * box.w + box.x;
|
|
rect.y = rect.y * box.h + box.y;
|
|
rect.w = rect.w * box.w;
|
|
rect.h = rect.h * box.h;
|
|
}
|
|
|
|
auto ctm = state.transform * transform;
|
|
auto scalex = std::sqrt(ctm.m00 * ctm.m00 + ctm.m01 * ctm.m01);
|
|
auto scaley = std::sqrt(ctm.m10 * ctm.m10 + ctm.m11 * ctm.m11);
|
|
|
|
auto width = rect.w * scalex;
|
|
auto height = rect.h * scaley;
|
|
|
|
RenderState newState(this, RenderMode::Display);
|
|
newState.canvas = Canvas::create(0, 0, width, height);
|
|
newState.transform = Transform::scaled(scalex, scaley);
|
|
|
|
if(viewBox.valid())
|
|
{
|
|
auto viewTransform = preserveAspectRatio.getMatrix(rect.w, rect.h, viewBox);
|
|
newState.transform.premultiply(viewTransform);
|
|
}
|
|
else if(contentUnits == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
newState.transform.scale(box.w, box.h);
|
|
}
|
|
|
|
auto transform = this->transform;
|
|
transform.translate(rect.x, rect.y);
|
|
transform.scale(1.0/scalex, 1.0/scaley);
|
|
|
|
renderChildren(newState);
|
|
state.canvas->setTexture(newState.canvas.get(), TextureType::Tiled, transform);
|
|
}
|
|
|
|
LayoutGradient::LayoutGradient(LayoutId id)
|
|
: LayoutObject(id)
|
|
{
|
|
}
|
|
|
|
LayoutLinearGradient::LayoutLinearGradient()
|
|
: LayoutGradient(LayoutId::LinearGradient)
|
|
{
|
|
}
|
|
|
|
void LayoutLinearGradient::apply(RenderState& state) const
|
|
{
|
|
auto transform = this->transform;
|
|
if(units == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
transform *= Transform(box.w, 0, 0, box.h, box.x, box.y);
|
|
}
|
|
|
|
state.canvas->setLinearGradient(x1, y1, x2, y2, stops, spreadMethod, transform);
|
|
}
|
|
|
|
LayoutRadialGradient::LayoutRadialGradient()
|
|
: LayoutGradient(LayoutId::RadialGradient)
|
|
{
|
|
}
|
|
|
|
void LayoutRadialGradient::apply(RenderState& state) const
|
|
{
|
|
auto transform = this->transform;
|
|
if(units == Units::ObjectBoundingBox)
|
|
{
|
|
const auto& box = state.objectBoundingBox();
|
|
transform *= Transform(box.w, 0, 0, box.h, box.x, box.y);
|
|
}
|
|
|
|
state.canvas->setRadialGradient(cx, cy, r, fx, fy, stops, spreadMethod, transform);
|
|
}
|
|
|
|
LayoutSolidColor::LayoutSolidColor()
|
|
: LayoutObject(LayoutId::SolidColor)
|
|
{
|
|
}
|
|
|
|
void LayoutSolidColor::apply(RenderState& state) const
|
|
{
|
|
state.canvas->setColor(color);
|
|
}
|
|
|
|
void FillData::fill(RenderState& state, const Path& path) const
|
|
{
|
|
if(opacity == 0.0 || (painter == nullptr && color.isNone()))
|
|
return;
|
|
|
|
if(painter == nullptr)
|
|
state.canvas->setColor(color);
|
|
else
|
|
painter->apply(state);
|
|
|
|
state.canvas->fill(path, state.transform, fillRule, BlendMode::Src_Over, opacity);
|
|
}
|
|
|
|
void StrokeData::stroke(RenderState& state, const Path& path) const
|
|
{
|
|
if(opacity == 0.0 || (painter == nullptr && color.isNone()))
|
|
return;
|
|
|
|
if(painter == nullptr)
|
|
state.canvas->setColor(color);
|
|
else
|
|
painter->apply(state);
|
|
|
|
state.canvas->stroke(path, state.transform, width, cap, join, miterlimit, dash, BlendMode::Src_Over, opacity);
|
|
}
|
|
|
|
static const double sqrt2 = 1.41421356237309504880;
|
|
|
|
void StrokeData::inflate(Rect& box) const
|
|
{
|
|
if(opacity == 0.0 || (painter == nullptr && color.isNone()))
|
|
return;
|
|
|
|
double caplimit = width / 2.0;
|
|
if(cap == LineCap::Square)
|
|
caplimit *= sqrt2;
|
|
|
|
double joinlimit = width / 2.0;
|
|
if(join == LineJoin::Miter)
|
|
joinlimit *= miterlimit;
|
|
|
|
double delta = std::max(caplimit, joinlimit);
|
|
box.x -= delta;
|
|
box.y -= delta;
|
|
box.w += delta * 2.0;
|
|
box.h += delta * 2.0;
|
|
}
|
|
|
|
MarkerPosition::MarkerPosition(const LayoutMarker* marker, const Point& origin, double angle)
|
|
: marker(marker), origin(origin), angle(angle)
|
|
{
|
|
}
|
|
|
|
void MarkerData::add(const LayoutMarker* marker, const Point& origin, double angle)
|
|
{
|
|
positions.emplace_back(marker, origin, angle);
|
|
}
|
|
|
|
void MarkerData::render(RenderState& state) const
|
|
{
|
|
for(const auto& position : positions)
|
|
position.marker->renderMarker(state, position.origin, position.angle, strokeWidth);
|
|
}
|
|
|
|
void MarkerData::inflate(Rect& box) const
|
|
{
|
|
for(const auto& position : positions)
|
|
box.unite(position.marker->markerBoundingBox(position.origin, position.angle, strokeWidth));
|
|
}
|
|
|
|
LayoutShape::LayoutShape()
|
|
: LayoutObject(LayoutId::Shape)
|
|
{
|
|
}
|
|
|
|
void LayoutShape::render(RenderState& state) const
|
|
{
|
|
if(visibility == Visibility::Hidden)
|
|
return;
|
|
|
|
BlendInfo info{clipper, masker, opacity, Rect::Invalid};
|
|
RenderState newState(this, state.mode());
|
|
newState.transform = transform * state.transform;
|
|
newState.beginGroup(state, info);
|
|
|
|
if(newState.mode() == RenderMode::Display)
|
|
{
|
|
fillData.fill(newState, path);
|
|
strokeData.stroke(newState, path);
|
|
markerData.render(newState);
|
|
}
|
|
else
|
|
{
|
|
newState.canvas->setColor(Color::Black);
|
|
newState.canvas->fill(path, newState.transform, clipRule, BlendMode::Src, 1.0);
|
|
}
|
|
|
|
newState.endGroup(state, info);
|
|
}
|
|
|
|
Rect LayoutShape::map(const Rect& rect) const
|
|
{
|
|
return transform.map(rect);
|
|
}
|
|
|
|
const Rect& LayoutShape::fillBoundingBox() const
|
|
{
|
|
if(m_fillBoundingBox.valid())
|
|
return m_fillBoundingBox;
|
|
|
|
m_fillBoundingBox = path.box();
|
|
return m_fillBoundingBox;
|
|
}
|
|
|
|
const Rect& LayoutShape::strokeBoundingBox() const
|
|
{
|
|
if(m_strokeBoundingBox.valid())
|
|
return m_strokeBoundingBox;
|
|
|
|
m_strokeBoundingBox = fillBoundingBox();
|
|
strokeData.inflate(m_strokeBoundingBox);
|
|
markerData.inflate(m_strokeBoundingBox);
|
|
return m_strokeBoundingBox;
|
|
}
|
|
|
|
RenderState::RenderState(const LayoutObject* object, RenderMode mode)
|
|
: m_object(object), m_mode(mode)
|
|
{
|
|
}
|
|
|
|
void RenderState::beginGroup(RenderState& state, const BlendInfo& info)
|
|
{
|
|
if(!info.clipper && !info.clip.valid() && (m_mode == RenderMode::Display && !(info.masker || info.opacity < 1.0)))
|
|
{
|
|
canvas = state.canvas;
|
|
return;
|
|
}
|
|
|
|
auto box = transform.map(m_object->strokeBoundingBox());
|
|
box.intersect(transform.map(info.clip));
|
|
box.intersect(state.canvas->box());
|
|
canvas = Canvas::create(box);
|
|
}
|
|
|
|
void RenderState::endGroup(RenderState& state, const BlendInfo& info)
|
|
{
|
|
if(state.canvas == canvas)
|
|
return;
|
|
|
|
if(info.clipper)
|
|
info.clipper->apply(*this);
|
|
|
|
if(info.masker && m_mode == RenderMode::Display)
|
|
info.masker->apply(*this);
|
|
|
|
if(info.clip.valid())
|
|
canvas->mask(info.clip, transform);
|
|
|
|
state.canvas->blend(canvas.get(), BlendMode::Src_Over, m_mode == RenderMode::Display ? info.opacity : 1.0);
|
|
}
|
|
|
|
LayoutContext::LayoutContext(const TreeBuilder* builder, LayoutSymbol* root)
|
|
: m_builder(builder), m_root(root)
|
|
{
|
|
}
|
|
|
|
Element* LayoutContext::getElementById(const std::string& id) const
|
|
{
|
|
return m_builder->getElementById(id);
|
|
}
|
|
|
|
LayoutObject* LayoutContext::getResourcesById(const std::string& id) const
|
|
{
|
|
auto it = m_resourcesCache.find(id);
|
|
if(it == m_resourcesCache.end())
|
|
return nullptr;
|
|
|
|
return it->second;
|
|
}
|
|
|
|
LayoutObject* LayoutContext::addToResourcesCache(const std::string& id, std::unique_ptr<LayoutObject> resources)
|
|
{
|
|
if(resources == nullptr)
|
|
return nullptr;
|
|
|
|
m_resourcesCache.emplace(id, resources.get());
|
|
return m_root->addChild(std::move(resources));
|
|
}
|
|
|
|
LayoutMask* LayoutContext::getMasker(const std::string& id)
|
|
{
|
|
if(id.empty())
|
|
return nullptr;
|
|
|
|
auto ref = getResourcesById(id);
|
|
if(ref && ref->id == LayoutId::Mask)
|
|
return static_cast<LayoutMask*>(ref);
|
|
|
|
auto element = getElementById(id);
|
|
if(element == nullptr || element->id != ElementID::Mask)
|
|
return nullptr;
|
|
|
|
auto masker = static_cast<MaskElement*>(element)->getMasker(this);
|
|
return static_cast<LayoutMask*>(addToResourcesCache(id, std::move(masker)));
|
|
}
|
|
|
|
LayoutClipPath* LayoutContext::getClipper(const std::string& id)
|
|
{
|
|
if(id.empty())
|
|
return nullptr;
|
|
|
|
auto ref = getResourcesById(id);
|
|
if(ref && ref->id == LayoutId::ClipPath)
|
|
return static_cast<LayoutClipPath*>(ref);
|
|
|
|
auto element = getElementById(id);
|
|
if(element == nullptr || element->id != ElementID::ClipPath)
|
|
return nullptr;
|
|
|
|
auto clipper = static_cast<ClipPathElement*>(element)->getClipper(this);
|
|
return static_cast<LayoutClipPath*>(addToResourcesCache(id, std::move(clipper)));
|
|
}
|
|
|
|
LayoutMarker* LayoutContext::getMarker(const std::string& id)
|
|
{
|
|
if(id.empty())
|
|
return nullptr;
|
|
|
|
auto ref = getResourcesById(id);
|
|
if(ref && ref->id == LayoutId::Marker)
|
|
return static_cast<LayoutMarker*>(ref);
|
|
|
|
auto element = getElementById(id);
|
|
if(element == nullptr || element->id != ElementID::Marker)
|
|
return nullptr;
|
|
|
|
auto marker = static_cast<MarkerElement*>(element)->getMarker(this);
|
|
return static_cast<LayoutMarker*>(addToResourcesCache(id, std::move(marker)));
|
|
}
|
|
|
|
LayoutObject* LayoutContext::getPainter(const std::string& id)
|
|
{
|
|
if(id.empty())
|
|
return nullptr;
|
|
|
|
auto ref = getResourcesById(id);
|
|
if(ref && ref->isPaint())
|
|
return ref;
|
|
|
|
auto element = getElementById(id);
|
|
if(element == nullptr || !element->isPaint())
|
|
return nullptr;
|
|
|
|
auto painter = static_cast<PaintElement*>(element)->getPainter(this);
|
|
return addToResourcesCache(id, std::move(painter));
|
|
}
|
|
|
|
FillData LayoutContext::fillData(const StyledElement* element)
|
|
{
|
|
auto fill = element->fill();
|
|
if(fill.isNone())
|
|
return FillData{};
|
|
|
|
FillData fillData;
|
|
fillData.painter = getPainter(fill.ref());
|
|
fillData.color = fill.color();
|
|
fillData.opacity = element->fill_opacity();
|
|
fillData.fillRule = element->fill_rule();
|
|
return fillData;
|
|
}
|
|
|
|
DashData LayoutContext::dashData(const StyledElement* element)
|
|
{
|
|
auto dasharray = element->stroke_dasharray();
|
|
if(dasharray.empty())
|
|
return DashData{};
|
|
|
|
LengthContext lengthContex(element);
|
|
DashArray dashes;
|
|
for(auto& dash : dasharray)
|
|
{
|
|
auto value = lengthContex.valueForLength(dash, LengthMode::Both);
|
|
dashes.push_back(value);
|
|
}
|
|
|
|
auto num_dash = dashes.size();
|
|
if(num_dash % 2)
|
|
num_dash *= 2;
|
|
|
|
DashData dashData;
|
|
dashData.array.resize(num_dash);
|
|
double sum = 0.0;
|
|
for(std::size_t i = 0;i < num_dash;i++)
|
|
{
|
|
dashData.array[i] = dashes[i % dashes.size()];
|
|
sum += dashData.array[i];
|
|
}
|
|
|
|
if(sum == 0.0)
|
|
return DashData{};
|
|
|
|
auto offset = lengthContex.valueForLength(element->stroke_dashoffset(), LengthMode::Both);
|
|
dashData.offset = std::fmod(offset, sum);
|
|
if(dashData.offset < 0.0)
|
|
dashData.offset += sum;
|
|
|
|
return dashData;
|
|
}
|
|
|
|
StrokeData LayoutContext::strokeData(const StyledElement* element)
|
|
{
|
|
auto stroke = element->stroke();
|
|
if(stroke.isNone())
|
|
return StrokeData{};
|
|
|
|
LengthContext lengthContex(element);
|
|
StrokeData strokeData;
|
|
strokeData.painter = getPainter(stroke.ref());
|
|
strokeData.color = stroke.color();
|
|
strokeData.opacity = element->stroke_opacity();
|
|
strokeData.width = lengthContex.valueForLength(element->stroke_width(), LengthMode::Both);
|
|
strokeData.miterlimit = element->stroke_miterlimit();
|
|
strokeData.cap = element->stroke_linecap();
|
|
strokeData.join = element->stroke_linejoin();
|
|
strokeData.dash = dashData(element);
|
|
return strokeData;
|
|
}
|
|
|
|
static const double pi = 3.14159265358979323846;
|
|
|
|
MarkerData LayoutContext::markerData(const GeometryElement* element, const Path& path)
|
|
{
|
|
auto markerStart = getMarker(element->marker_start());
|
|
auto markerMid = getMarker(element->marker_mid());
|
|
auto markerEnd = getMarker(element->marker_end());
|
|
|
|
if(markerStart == nullptr && markerMid == nullptr && markerEnd == nullptr)
|
|
return MarkerData{};
|
|
|
|
LengthContext lengthContex(element);
|
|
MarkerData markerData;
|
|
markerData.strokeWidth = lengthContex.valueForLength(element->stroke_width(), LengthMode::Both);
|
|
|
|
PathIterator it(path);
|
|
Point origin;
|
|
Point startPoint;
|
|
Point inslopePoints[2];
|
|
Point outslopePoints[2];
|
|
|
|
int index = 0;
|
|
std::array<Point, 3> points;
|
|
while(!it.isDone())
|
|
{
|
|
switch(it.currentSegment(points)) {
|
|
case PathCommand::MoveTo:
|
|
startPoint = points[0];
|
|
inslopePoints[0] = origin;
|
|
inslopePoints[1] = points[0];
|
|
origin = points[0];
|
|
break;
|
|
case PathCommand::LineTo:
|
|
inslopePoints[0] = origin;
|
|
inslopePoints[1] = points[0];
|
|
origin = points[0];
|
|
break;
|
|
case PathCommand::CubicTo:
|
|
inslopePoints[0] = points[1];
|
|
inslopePoints[1] = points[2];
|
|
origin = points[2];
|
|
break;
|
|
case PathCommand::Close:
|
|
inslopePoints[0] = origin;
|
|
inslopePoints[1] = points[0];
|
|
origin = startPoint;
|
|
startPoint = Point{};
|
|
break;
|
|
}
|
|
|
|
index += 1;
|
|
it.next();
|
|
|
|
if(!it.isDone() && (markerStart || markerMid))
|
|
{
|
|
it.currentSegment(points);
|
|
outslopePoints[0] = origin;
|
|
outslopePoints[1] = points[0];
|
|
|
|
if(index == 1 && markerStart)
|
|
{
|
|
Point slope{outslopePoints[1].x - outslopePoints[0].x, outslopePoints[1].y - outslopePoints[0].y};
|
|
auto angle = 180.0 * std::atan2(slope.y, slope.x) / pi;
|
|
|
|
markerData.add(markerStart, origin, angle);
|
|
}
|
|
|
|
if(index > 1 && markerMid)
|
|
{
|
|
Point inslope{inslopePoints[1].x - inslopePoints[0].x, inslopePoints[1].y - inslopePoints[0].y};
|
|
Point outslope{outslopePoints[1].x - outslopePoints[0].x, outslopePoints[1].y - outslopePoints[0].y};
|
|
auto inangle = 180.0 * std::atan2(inslope.y, inslope.x) / pi;
|
|
auto outangle = 180.0 * std::atan2(outslope.y, outslope.x) / pi;
|
|
auto angle = (inangle + outangle) * 0.5;
|
|
|
|
markerData.add(markerMid, origin, angle);
|
|
}
|
|
}
|
|
|
|
if(it.isDone() && markerEnd)
|
|
{
|
|
Point slope{inslopePoints[1].x - inslopePoints[0].x, inslopePoints[1].y - inslopePoints[0].y};
|
|
auto angle = 180.0 * std::atan2(slope.y, slope.x) / pi;
|
|
|
|
markerData.add(markerEnd, origin, angle);
|
|
}
|
|
}
|
|
|
|
return markerData;
|
|
}
|
|
|
|
void LayoutContext::addReference(const Element* element)
|
|
{
|
|
m_references.insert(element);
|
|
}
|
|
|
|
void LayoutContext::removeReference(const Element* element)
|
|
{
|
|
m_references.erase(element);
|
|
}
|
|
|
|
bool LayoutContext::hasReference(const Element* element) const
|
|
{
|
|
return m_references.count(element);
|
|
}
|
|
|
|
LayoutBreaker::LayoutBreaker(LayoutContext* context, const Element* element)
|
|
: m_context(context), m_element(element)
|
|
{
|
|
context->addReference(element);
|
|
}
|
|
|
|
LayoutBreaker::~LayoutBreaker()
|
|
{
|
|
m_context->removeReference(m_element);
|
|
}
|
|
|
|
} // namespace lunasvg
|