mirror of
				https://github.com/RetroDECK/ES-DE.git
				synced 2025-04-10 19:15:13 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			746 lines
		
	
	
		
			20 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			746 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
 | 
