├── .gitignore ├── .gitmodules ├── .travis.yml ├── CMakeLists.txt ├── Paper ├── BasicTypes.hpp ├── Components.hpp ├── Constants.hpp ├── Curve.cpp ├── Curve.hpp ├── CurveLocation.cpp ├── CurveLocation.hpp ├── Document.cpp ├── Document.hpp ├── G-code │ └── GCodeExport.hpp ├── Group.cpp ├── Group.hpp ├── Item.cpp ├── Item.hpp ├── Libs │ └── GL │ │ ├── gl3w.c │ │ ├── gl3w.h │ │ └── glcorearb.h ├── OpenGL │ ├── GLRenderer.cpp │ └── GLRenderer.hpp ├── Paint.cpp ├── Paint.hpp ├── Path.cpp ├── Path.hpp ├── PlacedSymbol.cpp ├── PlacedSymbol.hpp ├── Private │ ├── Allocator.hpp │ ├── BooleanOperations.cpp │ ├── BooleanOperations.hpp │ ├── ContainerView.hpp │ ├── JoinAndCap.cpp │ ├── JoinAndCap.hpp │ ├── PathFitter.cpp │ ├── PathFitter.hpp │ ├── PathFlattener.cpp │ ├── PathFlattener.hpp │ ├── Shape.cpp │ ├── Shape.hpp │ ├── StrokeTriangulator.cpp │ └── StrokeTriangulator.hpp ├── RenderInterface.cpp ├── RenderInterface.hpp ├── SVG │ ├── SVGExport.cpp │ ├── SVGExport.hpp │ ├── SVGImport.cpp │ ├── SVGImport.hpp │ ├── SVGImportResult.cpp │ └── SVGImportResult.hpp ├── Segment.cpp ├── Segment.hpp ├── Symbol.cpp ├── Symbol.hpp └── Tarp │ ├── TarpRenderer.cpp │ └── TarpRenderer.hpp ├── Playground ├── CMakeLists.txt └── PaperPlayground.cpp ├── README.md └── Tests ├── CMakeLists.txt └── PaperTests.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | .DS_Store -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "Submodules/Stick"] 2 | path = Submodules/Stick 3 | url = https://github.com/mokafolio/Stick.git 4 | [submodule "Submodules/Crunch"] 5 | path = Submodules/Crunch 6 | url = https://github.com/mokafolio/Crunch.git 7 | [submodule "Submodules/Brick"] 8 | path = Submodules/Brick 9 | url = https://github.com/mokafolio/Brick.git 10 | [submodule "Submodules/Scrub"] 11 | path = Submodules/Scrub 12 | url = https://github.com/mokafolio/Scrub.git 13 | [submodule "Submodules/Tarp"] 14 | path = Submodules/Tarp 15 | url = https://github.com/mokafolio/Tarp 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | # Ubuntu 14.04 Trusty support 2 | sudo: required 3 | dist: trusty 4 | language: cpp 5 | compiler: 6 | - clang 7 | - gcc 8 | install: 9 | - if [ "$CXX" = "g++" ]; then export CXX="g++-5"; fi 10 | - if [ "$CXX" == "clang++" ]; then export CXX="clang++-3.8"; fi 11 | before_script: 12 | - mkdir build 13 | - cd build 14 | - cmake -DBuildSubmodules=On .. 15 | addons: 16 | apt: 17 | sources: 18 | - ubuntu-toolchain-r-test 19 | packages: 20 | - gcc-5 21 | - g++-5 22 | - clang-3.8 23 | - lldb-3.8 24 | 25 | script: make check 26 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required (VERSION 2.8.11) 2 | project (Paper) 3 | set(CMAKE_CXX_FLAGS "-std=c++11 -fno-exceptions -w") 4 | 5 | option(BuildSubmodules "BuildSubmodules" OFF) 6 | option(AddTests "AddTests" ON) 7 | 8 | find_package(OpenGL REQUIRED) 9 | 10 | if(BuildSubmodules) 11 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/Libs ${CMAKE_CURRENT_SOURCE_DIR}/Submodules ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Stick ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Crunch ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Brick ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Scrub) 12 | else() 13 | include_directories (${CMAKE_CURRENT_SOURCE_DIR} /usr/local/include ${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Tarp ${CMAKE_CURRENT_SOURCE_DIR}/Paper/Libs) 14 | endif() 15 | 16 | link_directories(/usr/local/lib ${CMAKE_INSTALL_PREFIX}/lib) 17 | 18 | set (PAPERDEPS Stick Brick Scrub ${OPENGL_LIBRARIES}) 19 | 20 | set (PAPERINC 21 | Paper/BasicTypes.hpp 22 | Paper/Components.hpp 23 | Paper/Constants.hpp 24 | Paper/Curve.hpp 25 | Paper/CurveLocation.hpp 26 | Paper/Document.hpp 27 | Paper/Group.hpp 28 | Paper/Item.hpp 29 | Paper/Paint.hpp 30 | Paper/Path.hpp 31 | Paper/PlacedSymbol.hpp 32 | Paper/RenderInterface.hpp 33 | Paper/Segment.hpp 34 | Paper/Symbol.hpp 35 | Paper/Libs/GL/gl3w.h 36 | #Paper/OpenGL/GLRenderer.hpp 37 | Paper/Private/Allocator.hpp 38 | Paper/Private/BooleanOperations.hpp 39 | Paper/Private/ContainerView.hpp 40 | Paper/Private/JoinAndCap.hpp 41 | Paper/Private/PathFitter.hpp 42 | Paper/Private/PathFlattener.hpp 43 | Paper/Private/Shape.hpp 44 | Paper/Private/StrokeTriangulator.hpp 45 | Paper/SVG/SVGExport.hpp 46 | Paper/SVG/SVGImport.hpp 47 | Paper/SVG/SVGImportResult.hpp 48 | Paper/Tarp/TarpRenderer.hpp 49 | ) 50 | 51 | set (PAPERSRC 52 | Paper/Curve.cpp 53 | Paper/CurveLocation.cpp 54 | Paper/Document.cpp 55 | Paper/Group.cpp 56 | Paper/Item.cpp 57 | Paper/Paint.cpp 58 | Paper/Path.cpp 59 | Paper/PlacedSymbol.cpp 60 | Paper/RenderInterface.cpp 61 | Paper/Segment.cpp 62 | Paper/Symbol.cpp 63 | Paper/Libs/GL/gl3w.c 64 | #Paper/OpenGL/GLRenderer.cpp 65 | Paper/Private/BooleanOperations.cpp 66 | Paper/Private/JoinAndCap.cpp 67 | Paper/Private/PathFitter.cpp 68 | Paper/Private/PathFlattener.cpp 69 | Paper/Private/Shape.cpp 70 | Paper/Private/StrokeTriangulator.cpp 71 | Paper/SVG/SVGExport.cpp 72 | Paper/SVG/SVGImport.cpp 73 | Paper/SVG/SVGImportResult.cpp 74 | Paper/Tarp/TarpRenderer.cpp 75 | ) 76 | 77 | if(BuildSubmodules) 78 | set(PrevAddTests ${AddTests}) 79 | set(AddTests OFF) 80 | set(BuildSubmodules OFF) #we don't want to build submodules of submodules 81 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Stick) 82 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Crunch) 83 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Brick) 84 | add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Submodules/Scrub) 85 | set(AddTests ${PrevAddTests}) 86 | endif() 87 | 88 | add_library (Paper SHARED ${PAPERSRC}) 89 | add_library (PaperStatic STATIC ${PAPERSRC}) 90 | target_link_libraries(Paper ${PAPERDEPS}) 91 | target_link_libraries(PaperStatic ${PAPERDEPS}) 92 | install (TARGETS Paper PaperStatic DESTINATION ${CMAKE_INSTALL_PREFIX}/lib) 93 | foreach ( file ${PAPERINC} ) 94 | get_filename_component( dir ${file} DIRECTORY ) 95 | install( FILES ${file} DESTINATION ${CMAKE_INSTALL_PREFIX}/include/${dir} ) 96 | endforeach() 97 | if(AddTests) 98 | add_subdirectory (Tests) 99 | endif() 100 | add_subdirectory (Playground) 101 | -------------------------------------------------------------------------------- /Paper/BasicTypes.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_BASICTYPES_HPP 2 | #define PAPER_BASICTYPES_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | namespace paper 17 | { 18 | using Float = stick::Float32; 19 | using Vec2f = crunch::Vector2; 20 | using Mat3f = crunch::Matrix3; 21 | using Mat4f = crunch::Matrix4; 22 | using Rect = crunch::Rectangle; 23 | using Bezier = crunch::BezierCubic; 24 | using Line = crunch::Line; 25 | using ColorRGB = crunch::ColorRGB; 26 | using ColorRGBA = crunch::ColorRGBA; 27 | using ColorHSB = crunch::ColorHSB; 28 | using ColorHSBA = crunch::ColorHSBA; 29 | class Segment; 30 | class Curve; 31 | using SegmentArray = stick::DynamicArray>; 32 | using CurveArray = stick::DynamicArray>; 33 | using DashArray = stick::DynamicArray; 34 | } 35 | 36 | #endif //PAPER_BASICTYPES_HPP 37 | -------------------------------------------------------------------------------- /Paper/Components.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_COMPONENTS_HPP 2 | #define PAPER_COMPONENTS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace paper 11 | { 12 | class Document; 13 | class Item; 14 | using ItemArray = stick::DynamicArray; 15 | 16 | namespace comps 17 | { 18 | //@TODO: I think we can group a lot of these together in the future to simplify 19 | //default components and possibly get a speed boost by adding the ones together 20 | //that are usually accessed together. 21 | using ItemType = brick::Component; 22 | using HubPointer = brick::Component; 23 | using Doc = brick::Component; 24 | using Parent = brick::Component; 25 | using Name = brick::Component; 26 | using Children = brick::Component; 27 | using Transform = brick::Component; 28 | using AbsoluteTransform = brick::Component; 29 | using AbsoluteTransformDirtyFlag = brick::Component; 30 | using Fill = brick::Component; 31 | using Stroke = brick::Component; 32 | using StrokeWidth = brick::Component; 33 | using StrokeJoin = brick::Component; 34 | using StrokeCap = brick::Component; 35 | using ScalingStrokeFlag = brick::Component; 36 | using MiterLimit = brick::Component; 37 | using DashArray = brick::Component; 38 | using DashOffset = brick::Component; 39 | using WindingRule = brick::Component; 40 | using StrokeGeometryDirtyFlag = brick::Component; 41 | using FillGeometryDirtyFlag = brick::Component; 42 | using BoundsGeometryDirtyFlag = brick::Component; 43 | using VisibilityFlag = brick::Component; 44 | using RemeshOnTransformChange = brick::Component; 45 | 46 | struct BoundsData 47 | { 48 | bool bDirty; 49 | Rect bounds; 50 | }; 51 | 52 | using HandleBounds = brick::Component; 53 | using StrokeBounds = brick::Component; 54 | using Bounds = brick::Component; 55 | using LocalBounds = brick::Component; 56 | using Pivot = brick::Component; 57 | 58 | struct DecomposedData 59 | { 60 | Vec2f translation; 61 | Float rotation; 62 | Vec2f scaling; 63 | }; 64 | 65 | using DecomposedTransform = brick::Component; 66 | using AbsoluteDecomposedTransform = brick::Component; 67 | 68 | //document specific components 69 | using DocumentSize = brick::Component; 70 | 71 | //group specific components 72 | using ClippedFlag = brick::Component; 73 | 74 | //Path specific components 75 | using Segments = brick::Component; 76 | using Curves = brick::Component; 77 | using ClosedFlag = brick::Component; 78 | struct PathLengthData 79 | { 80 | bool bDirty; 81 | Float length; 82 | }; 83 | using PathLength = brick::Component; 84 | 85 | //type list of all components. Because C++ is shit, we need to manually maintain it 86 | //as there is no decent way to my knowledge to automatically generate this at compile time. 87 | using ComponentTypeList = typename stick::MakeTypeList < 88 | ItemType, 89 | HubPointer, 90 | Doc, 91 | Parent, 92 | Name, 93 | Children, 94 | Transform, 95 | AbsoluteTransform, 96 | AbsoluteTransformDirtyFlag, 97 | Fill, 98 | Stroke, 99 | StrokeWidth, 100 | StrokeJoin, 101 | StrokeCap, 102 | ScalingStrokeFlag, 103 | MiterLimit, 104 | DashArray, 105 | DashOffset, 106 | WindingRule, 107 | StrokeGeometryDirtyFlag, 108 | FillGeometryDirtyFlag, 109 | BoundsGeometryDirtyFlag, 110 | VisibilityFlag, 111 | RemeshOnTransformChange, 112 | HandleBounds, 113 | StrokeBounds, 114 | Bounds, 115 | LocalBounds, 116 | Pivot, 117 | DecomposedTransform, 118 | AbsoluteDecomposedTransform, 119 | DocumentSize, 120 | ClippedFlag, 121 | Segments, 122 | Curves, 123 | ClosedFlag, 124 | PathLength 125 | >::List; 126 | } 127 | } 128 | 129 | #endif //PAPER_COMPONENTS_HPP 130 | -------------------------------------------------------------------------------- /Paper/Constants.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_CONSTANTS_HPP 2 | #define PAPER_CONSTANTS_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace paper 9 | { 10 | STICK_API_ENUM_CLASS(EntityType) 11 | { 12 | Document, 13 | Group, 14 | Path, 15 | PlacedSymbol, 16 | Unknown 17 | }; 18 | 19 | STICK_API_ENUM_CLASS(StrokeCap) 20 | { 21 | Round, 22 | Square, 23 | Butt 24 | }; 25 | 26 | STICK_API_ENUM_CLASS(StrokeJoin) 27 | { 28 | Miter, 29 | Round, 30 | Bevel 31 | }; 32 | 33 | STICK_API_ENUM_CLASS(WindingRule) 34 | { 35 | EvenOdd, 36 | NonZero 37 | }; 38 | 39 | STICK_API_ENUM_CLASS(Smoothing) 40 | { 41 | Continuous, 42 | Asymmetric, 43 | CatmullRom, 44 | Geometric 45 | }; 46 | 47 | namespace detail 48 | { 49 | //@TODO: Adjust these for Float32 / Float64 50 | //@TODO: These need a lot more work, checking and tests :( 51 | class PaperConstants 52 | { 53 | public: 54 | 55 | static Float kappa() 56 | { 57 | // Kappa, see: http://www.whizkidtech.redprince.net/bezier/circle/kappa/ 58 | static const Float s_kappa = 4.0 * (crunch::sqrt(2.0) - 1.0) / 3.0; 59 | return s_kappa; 60 | } 61 | 62 | static Float tolerance() 63 | { 64 | return static_cast(1e-4); 65 | } 66 | 67 | static Float epsilon() 68 | { 69 | return std::numeric_limits::epsilon(); 70 | } 71 | 72 | static Float curveTimeEpsilon() 73 | { 74 | return static_cast(4e-4); 75 | } 76 | 77 | static Float geometricEpsilon() 78 | { 79 | return static_cast(2e-4); 80 | } 81 | 82 | static Float windingEpsilon() 83 | { 84 | return static_cast(2e-4); 85 | } 86 | 87 | static Float trigonometricEpsilon() 88 | { 89 | return static_cast(1e-5); 90 | } 91 | 92 | static Float clippingEpsilon() 93 | { 94 | return static_cast(1e-7); 95 | } 96 | }; 97 | } 98 | } 99 | 100 | #endif //PAPER_CONSTANTS_HPP 101 | -------------------------------------------------------------------------------- /Paper/Curve.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | Curve::Curve() 9 | { 10 | 11 | } 12 | 13 | Curve::Curve(const Path & _path, stick::Size _segmentA, stick::Size _segmentB) : 14 | m_path(_path), 15 | m_segmentA(_segmentA), 16 | m_segmentB(_segmentB), 17 | m_bLengthCached(false), 18 | m_bBoundsCached(false), 19 | m_length(0) 20 | { 21 | m_curve = Bezier(positionOne(), handleOneAbsolute(), handleTwoAbsolute(), positionTwo()); 22 | } 23 | 24 | Path Curve::path() const 25 | { 26 | return m_path; 27 | } 28 | 29 | void Curve::setPositionOne(const Vec2f & _vec) 30 | { 31 | segmentOne().setPosition(_vec); 32 | } 33 | 34 | void Curve::setHandleOne(const Vec2f & _vec) 35 | { 36 | segmentOne().setHandleOut(_vec); 37 | } 38 | 39 | void Curve::setPositionTwo(const Vec2f & _vec) 40 | { 41 | segmentTwo().setPosition(_vec); 42 | } 43 | 44 | void Curve::setHandleTwo(const Vec2f & _vec) 45 | { 46 | segmentTwo().setHandleIn(_vec); 47 | } 48 | 49 | Segment & Curve::segmentOne() 50 | { 51 | return *m_path.segmentArray()[m_segmentA]; 52 | } 53 | 54 | const Segment & Curve::segmentOne() const 55 | { 56 | return *m_path.segmentArray()[m_segmentA]; 57 | } 58 | 59 | Segment & Curve::segmentTwo() 60 | { 61 | return *m_path.segmentArray()[m_segmentB]; 62 | } 63 | 64 | const Segment & Curve::segmentTwo() const 65 | { 66 | return *m_path.segmentArray()[m_segmentB]; 67 | } 68 | 69 | const Vec2f & Curve::positionOne() const 70 | { 71 | return segmentOne().position(); 72 | } 73 | 74 | const Vec2f & Curve::positionTwo() const 75 | { 76 | return segmentTwo().position(); 77 | } 78 | 79 | const Vec2f & Curve::handleOne() const 80 | { 81 | return segmentOne().handleOut(); 82 | } 83 | 84 | Vec2f Curve::handleOneAbsolute() const 85 | { 86 | return segmentOne().handleOutAbsolute(); 87 | } 88 | 89 | const Vec2f & Curve::handleTwo() const 90 | { 91 | return segmentTwo().handleIn(); 92 | } 93 | 94 | Vec2f Curve::handleTwoAbsolute() const 95 | { 96 | return segmentTwo().handleInAbsolute(); 97 | } 98 | 99 | Vec2f Curve::positionAt(Float _offset) const 100 | { 101 | return m_curve.positionAt(parameterAtOffset(_offset)); 102 | } 103 | 104 | Vec2f Curve::normalAt(Float _offset) const 105 | { 106 | return m_curve.normalAt(parameterAtOffset(_offset)); 107 | } 108 | 109 | Vec2f Curve::tangentAt(Float _offset) const 110 | { 111 | return m_curve.tangentAt(parameterAtOffset(_offset)); 112 | } 113 | 114 | Float Curve::curvatureAt(Float _offset) const 115 | { 116 | return m_curve.curvatureAt(parameterAtOffset(_offset)); 117 | } 118 | 119 | Float Curve::angleAt(Float _offset) const 120 | { 121 | return m_curve.angleAt(parameterAtOffset(_offset)); 122 | } 123 | 124 | stick::Maybe Curve::divideAt(Float _offset) 125 | { 126 | return divideAtParameter(parameterAtOffset(_offset)); 127 | } 128 | 129 | Vec2f Curve::positionAtParameter(Float _t) const 130 | { 131 | return m_curve.positionAt(_t); 132 | } 133 | 134 | Vec2f Curve::normalAtParameter(Float _t) const 135 | { 136 | return m_curve.normalAt(_t); 137 | } 138 | 139 | Vec2f Curve::tangentAtParameter(Float _t) const 140 | { 141 | return m_curve.tangentAt(_t); 142 | } 143 | 144 | Float Curve::curvatureAtParameter(Float _t) const 145 | { 146 | return m_curve.curvatureAt(_t); 147 | } 148 | 149 | Float Curve::angleAtParameter(Float _t) const 150 | { 151 | return m_curve.angleAt(_t); 152 | } 153 | 154 | stick::Maybe Curve::divideAtParameter(Float _t) 155 | { 156 | // return empty maybe if _t is out of range 157 | if (_t >= 1 || _t <= 0) 158 | return stick::Maybe(); 159 | 160 | // split the bezier 161 | auto splitResult = m_curve.subdivide(_t); 162 | 163 | //adjust the exiting segments of this curve 164 | segmentOne().m_handleOut = splitResult.first.handleOne() - splitResult.first.positionOne(); 165 | segmentTwo().m_handleIn = splitResult.second.handleTwo() - splitResult.second.positionTwo(); 166 | segmentTwo().m_position = splitResult.second.positionTwo(); 167 | 168 | // create the new segment 169 | stick::Size sindex = segmentOne().m_index + 1; 170 | auto it = m_segmentB != 0 ? m_path.segmentArray().begin() + m_segmentB : m_path.segmentArray().end(); 171 | // m_path.segmentArray().insert(it, 172 | // stick::UniquePtr(m_path.document().allocator().create(m_path, splitResult.first.positionTwo(), 173 | // splitResult.first.handleTwo() - splitResult.first.positionTwo(), 174 | // splitResult.second.handleOne() - splitResult.second.positionOne(), sindex))); 175 | 176 | m_path.segmentArray().insert(it, stick::makeUnique(m_path.document().allocator(), m_path, splitResult.first.positionTwo(), 177 | splitResult.first.handleTwo() - splitResult.first.positionTwo(), 178 | splitResult.second.handleOne() - splitResult.second.positionOne(), sindex)); 179 | 180 | 181 | // adjust the segment indices 182 | auto & segs = m_path.get(); 183 | for (stick::Size i = sindex + 1; i < segs.count(); ++i) 184 | { 185 | segs[i]->m_index += 1; 186 | } 187 | 188 | // create the new curve in the correct index 189 | auto cit = sindex < m_path.curveArray().count() ? m_path.curveArray().begin() + sindex : m_path.curveArray().end(); 190 | stick::Size sindex2 = cit == m_path.curveArray().end() ? 0 : sindex + 1; 191 | Curve * c = m_path.document().allocator().create(m_path, sindex, sindex2); 192 | auto rit = m_path.curveArray().insert(cit, stick::UniquePtr(c, m_path.document().allocator())); 193 | 194 | // update curve segment indices 195 | auto & curves = m_path.curveArray(); 196 | if (sindex2 != 0) 197 | { 198 | for (stick::Size i = m_segmentB + 1; i < curves.count(); ++i) 199 | { 200 | curves[i]->m_segmentA += 1; 201 | if (curves[i]->m_segmentB != 0 || i < curves.count() - 1) 202 | curves[i]->m_segmentB = curves[i]->m_segmentA + 1; 203 | } 204 | } 205 | else 206 | { 207 | //only update the previous closing curves indices 208 | curves[curves.count() - 2]->m_segmentB = sindex; 209 | } 210 | 211 | //mark the affected curves dirty 212 | m_segmentB = sindex; 213 | markDirty(); 214 | c->markDirty(); 215 | 216 | // return the new curve 217 | return *c; 218 | } 219 | 220 | Float Curve::parameterAtOffset(Float _offset) const 221 | { 222 | return m_curve.parameterAtOffset(_offset); 223 | } 224 | 225 | Float Curve::closestParameter(const Vec2f & _point) const 226 | { 227 | return m_curve.closestParameter(_point); 228 | } 229 | 230 | Float Curve::closestParameter(const Vec2f & _point, Float & _outDistance) const 231 | { 232 | return m_curve.closestParameter(_point, _outDistance, 0, 1, 0); 233 | } 234 | 235 | Float Curve::lengthBetween(Float _tStart, Float _tEnd) const 236 | { 237 | return m_curve.lengthBetween(_tStart, _tEnd); 238 | } 239 | 240 | Float Curve::pathOffset() const 241 | { 242 | //calculate the offset from the start to the curve location 243 | Float offset = 0; 244 | for (const auto & c : m_path.curveArray()) 245 | { 246 | if (c.get() != this) 247 | offset += c->length(); 248 | else 249 | break; 250 | } 251 | 252 | return offset; 253 | } 254 | 255 | CurveLocation Curve::closestCurveLocation(const Vec2f & _point) const 256 | { 257 | Float t = closestParameter(_point); 258 | return CurveLocation(const_cast(*this), t, pathOffset() + lengthBetween(0, t)); 259 | } 260 | 261 | CurveLocation Curve::curveLocationAt(Float _offset) const 262 | { 263 | return CurveLocation(const_cast(*this), parameterAtOffset(_offset), pathOffset() + _offset); 264 | } 265 | 266 | CurveLocation Curve::curveLocationAtParameter(Float _t) const 267 | { 268 | return CurveLocation(const_cast(*this), _t, pathOffset() + lengthBetween(0, _t)); 269 | } 270 | 271 | bool Curve::isLinear() const 272 | { 273 | return crunch::isClose(handleOne(), Vec2f(0)) && crunch::isClose(handleTwo(), Vec2f(0)); 274 | } 275 | 276 | bool Curve::isStraight() const 277 | { 278 | if (isLinear()) 279 | return true; 280 | 281 | Vec2f line = positionTwo() - positionOne(); 282 | 283 | // Zero-length line, with some handles defined. 284 | if (crunch::isClose(line, Vec2f(0))) 285 | return false; 286 | 287 | if (crunch::isCollinear(handleOne(), line) && crunch::isCollinear(handleTwo(), line)) 288 | { 289 | // Collinear handles. Project them onto line to see if they are 290 | // within the line's range: 291 | Float d = crunch::dot(line, line); 292 | Float p1 = crunch::dot(line, handleOne()) / d; 293 | Float p2 = crunch::dot(line, handleTwo()) / d; 294 | 295 | return p1 >= 0 && p1 <= 1 && p2 <= 0 && p2 >= -1; 296 | } 297 | 298 | return false; 299 | } 300 | 301 | bool Curve::isArc() const 302 | { 303 | if (crunch::isOrthogonal(handleOne(), handleTwo(), detail::PaperConstants::tolerance())) 304 | { 305 | Line aLine(positionOne(), handleOneAbsolute()); 306 | Line bLine(positionTwo(), handleTwoAbsolute()); 307 | 308 | auto result = crunch::intersect(aLine, bLine); 309 | if (result) 310 | { 311 | Vec2f corner = *result; 312 | static Float kappa = detail::PaperConstants::kappa(); 313 | static Float epsilon = detail::PaperConstants::epsilon(); 314 | 315 | if (crunch::isClose(crunch::length(handleOne()) / crunch::length(corner - positionOne()) - kappa, 316 | 0.0f, epsilon) && 317 | crunch::isClose(crunch::length(handleTwo()) / crunch::length(corner - positionTwo()) - kappa, 318 | 0.0f, epsilon)) 319 | return true; 320 | } 321 | } 322 | return false; 323 | } 324 | 325 | bool Curve::isOrthogonal(const Curve & _other) const 326 | { 327 | if (isLinear() && _other.isLinear()) 328 | { 329 | if (crunch::isOrthogonal(positionOne() - positionTwo(), _other.positionOne() - _other.positionTwo(), detail::PaperConstants::tolerance())) 330 | return true; 331 | } 332 | return false; 333 | } 334 | 335 | bool Curve::isCollinear(const Curve & _other) const 336 | { 337 | if (isLinear() && _other.isLinear()) 338 | { 339 | if (crunch::isCollinear(positionOne() - positionTwo(), _other.positionOne() - _other.positionTwo(), detail::PaperConstants::tolerance())) 340 | return true; 341 | } 342 | return false; 343 | } 344 | 345 | Float Curve::length() const 346 | { 347 | if (!m_bLengthCached) 348 | { 349 | m_length = m_curve.length(); 350 | m_bLengthCached = true; 351 | } 352 | return m_length; 353 | } 354 | 355 | Float Curve::area() const 356 | { 357 | return m_curve.area(); 358 | } 359 | 360 | const Rect & Curve::bounds() const 361 | { 362 | if (!m_bBoundsCached) 363 | { 364 | m_bounds = m_curve.bounds(); 365 | m_bBoundsCached = true; 366 | } 367 | return m_bounds; 368 | } 369 | 370 | Rect Curve::bounds(Float _padding) const 371 | { 372 | return m_curve.bounds(_padding); 373 | } 374 | 375 | void Curve::markDirty() 376 | { 377 | m_bLengthCached = false; 378 | m_bBoundsCached = false; 379 | m_curve = Bezier(positionOne(), handleOneAbsolute(), handleTwoAbsolute(), positionTwo()); 380 | } 381 | 382 | const Bezier & Curve::bezier() const 383 | { 384 | return m_curve; 385 | } 386 | 387 | void Curve::peaks(stick::DynamicArray & _peaks) const 388 | { 389 | auto peaks = m_curve.peaks(); 390 | for (stick::Int32 i = 0; i < peaks.count; ++i) 391 | _peaks.append(peaks.values[i]); 392 | } 393 | 394 | void Curve::extrema(stick::DynamicArray & _extrema) const 395 | { 396 | auto ex = m_curve.extrema2D(); 397 | for (stick::Int32 i = 0; i < ex.count; ++i) 398 | _extrema.append(ex.values[i]); 399 | } 400 | } 401 | -------------------------------------------------------------------------------- /Paper/Curve.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_CURVE_HPP 2 | #define PAPER_CURVE_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class CurveLocation; 9 | class Segment; 10 | 11 | class STICK_API Curve 12 | { 13 | friend class Path; 14 | friend class Segment; 15 | 16 | public: 17 | 18 | Curve(); 19 | 20 | Curve(const Path & _path, stick::Size _segmentA, stick::Size _segmentB); 21 | 22 | Path path() const; 23 | 24 | void setPositionOne(const Vec2f & _vec); 25 | 26 | void setHandleOne(const Vec2f & _vec); 27 | 28 | void setPositionTwo(const Vec2f & _vec); 29 | 30 | void setHandleTwo(const Vec2f & _vec); 31 | 32 | const Vec2f & positionOne() const; 33 | 34 | const Vec2f & positionTwo() const; 35 | 36 | const Vec2f & handleOne() const; 37 | 38 | Vec2f handleOneAbsolute() const; 39 | 40 | const Vec2f & handleTwo() const; 41 | 42 | Vec2f handleTwoAbsolute() const; 43 | 44 | Segment & segmentOne(); 45 | 46 | const Segment & segmentOne() const; 47 | 48 | Segment & segmentTwo(); 49 | 50 | const Segment & segmentTwo() const; 51 | 52 | 53 | Vec2f positionAt(Float _offset) const; 54 | 55 | Vec2f normalAt(Float _offset) const; 56 | 57 | Vec2f tangentAt(Float _offset) const; 58 | 59 | Float curvatureAt(Float _offset) const; 60 | 61 | Float angleAt(Float _offset) const; 62 | 63 | stick::Maybe divideAt(Float _offset); 64 | 65 | Vec2f positionAtParameter(Float _t) const; 66 | 67 | Vec2f normalAtParameter(Float _t) const; 68 | 69 | Vec2f tangentAtParameter(Float _t) const; 70 | 71 | Float curvatureAtParameter(Float _t) const; 72 | 73 | Float angleAtParameter(Float _t) const; 74 | 75 | stick::Maybe divideAtParameter(Float _t); 76 | 77 | Float parameterAtOffset(Float _offset) const; 78 | 79 | Float closestParameter(const Vec2f & _point) const; 80 | 81 | Float closestParameter(const Vec2f & _point, Float & _outDistance) const; 82 | 83 | Float lengthBetween(Float _tStart, Float _tEnd) const; 84 | 85 | Float pathOffset() const; 86 | 87 | void peaks(stick::DynamicArray & _peaks) const; 88 | 89 | void extrema(stick::DynamicArray & _extrema) const; 90 | 91 | 92 | CurveLocation closestCurveLocation(const Vec2f & _point) const; 93 | 94 | CurveLocation curveLocationAt(Float _offset) const; 95 | 96 | CurveLocation curveLocationAtParameter(Float _t) const; 97 | 98 | 99 | bool isLinear() const; 100 | 101 | bool isStraight() const; 102 | 103 | bool isArc() const; 104 | 105 | bool isOrthogonal(const Curve & _other) const; 106 | 107 | bool isCollinear(const Curve & _other) const; 108 | 109 | Float length() const; 110 | 111 | Float area() const; 112 | 113 | const Rect & bounds() const; 114 | 115 | Rect bounds(Float _padding) const; 116 | 117 | const Bezier & bezier() const; 118 | 119 | 120 | private: 121 | 122 | void markDirty(); 123 | 124 | 125 | Path m_path; 126 | stick::Size m_segmentA; 127 | stick::Size m_segmentB; 128 | Bezier m_curve; 129 | mutable bool m_bLengthCached; 130 | mutable bool m_bBoundsCached; 131 | mutable Float m_length; 132 | mutable Rect m_bounds; 133 | }; 134 | } 135 | 136 | #endif //PAPER_CURVE_HPP 137 | -------------------------------------------------------------------------------- /Paper/CurveLocation.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace paper 5 | { 6 | CurveLocation::CurveLocation() : 7 | m_curve(nullptr) 8 | { 9 | 10 | } 11 | 12 | CurveLocation::CurveLocation(Curve & _c, Float _parameter, Float _offset) : 13 | m_curve(&_c), 14 | m_parameter(_parameter), 15 | m_offset(_offset) 16 | { 17 | 18 | } 19 | 20 | CurveLocation::operator bool() const 21 | { 22 | return m_curve; 23 | } 24 | 25 | bool CurveLocation::operator == (const CurveLocation & _other) const 26 | { 27 | return m_curve == _other.m_curve && m_parameter == _other.m_parameter; 28 | } 29 | 30 | bool CurveLocation::operator != (const CurveLocation & _other) const 31 | { 32 | return !(*this == _other); 33 | } 34 | 35 | bool CurveLocation::isSynonymous(const CurveLocation & _other) 36 | { 37 | if (isValid() && _other.isValid()) 38 | { 39 | if (_other.curve().path() == curve().path()) 40 | { 41 | printf("SAME PATH\n"); 42 | Float diff = std::abs(m_offset - _other.m_offset); 43 | if (diff < detail::PaperConstants::geometricEpsilon() || 44 | std::abs(curve().path().length() - diff) < detail::PaperConstants::geometricEpsilon()) 45 | { 46 | printf("SYNOONONASIOGHAKSH\n"); 47 | return true; 48 | } 49 | } 50 | } 51 | return false; 52 | } 53 | 54 | Vec2f CurveLocation::position() const 55 | { 56 | STICK_ASSERT(m_curve); 57 | return m_curve->positionAtParameter(m_parameter); 58 | } 59 | 60 | Vec2f CurveLocation::normal() const 61 | { 62 | STICK_ASSERT(m_curve); 63 | return m_curve->normalAtParameter(m_parameter); 64 | } 65 | 66 | Vec2f CurveLocation::tangent() const 67 | { 68 | STICK_ASSERT(m_curve); 69 | return m_curve->tangentAtParameter(m_parameter); 70 | } 71 | 72 | Float CurveLocation::curvature() const 73 | { 74 | STICK_ASSERT(m_curve); 75 | return m_curve->curvatureAtParameter(m_parameter); 76 | } 77 | 78 | Float CurveLocation::angle() const 79 | { 80 | STICK_ASSERT(m_curve); 81 | return m_curve->angleAtParameter(m_parameter); 82 | } 83 | 84 | Float CurveLocation::parameter() const 85 | { 86 | return m_parameter; 87 | } 88 | 89 | Float CurveLocation::offset() const 90 | { 91 | return m_offset; 92 | } 93 | 94 | bool CurveLocation::isValid() const 95 | { 96 | return m_curve; 97 | } 98 | 99 | const Curve & CurveLocation::curve() const 100 | { 101 | STICK_ASSERT(m_curve); 102 | return *m_curve; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /Paper/CurveLocation.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_CURVELOCATION_HPP 2 | #define PAPER_CURVELOCATION_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class Curve; 9 | 10 | class STICK_API CurveLocation 11 | { 12 | friend class Curve; 13 | 14 | public: 15 | 16 | CurveLocation(); 17 | 18 | explicit operator bool() const; 19 | 20 | bool operator == (const CurveLocation & _other) const; 21 | 22 | bool operator != (const CurveLocation & _other) const; 23 | 24 | bool isSynonymous(const CurveLocation & _other); 25 | 26 | Vec2f position() const; 27 | 28 | Vec2f normal() const; 29 | 30 | Vec2f tangent() const; 31 | 32 | Float curvature() const; 33 | 34 | Float angle() const; 35 | 36 | Float parameter() const; 37 | 38 | Float offset() const; 39 | 40 | bool isValid() const; 41 | 42 | const Curve & curve() const; 43 | 44 | private: 45 | 46 | CurveLocation(Curve & _c, Float _parameter, Float _offset); 47 | 48 | Curve * m_curve; 49 | Float m_parameter; 50 | Float m_offset; 51 | }; 52 | 53 | struct STICK_API Intersection 54 | { 55 | CurveLocation location; 56 | Vec2f position; 57 | }; 58 | } 59 | 60 | #endif //PAPER_CURVELOCATION_HPP 61 | -------------------------------------------------------------------------------- /Paper/Document.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | namespace paper 10 | { 11 | using namespace stick; 12 | 13 | Document::Document() 14 | { 15 | 16 | } 17 | 18 | // NoPaint Document::createNoPaint() 19 | // { 20 | // brick::Hub * hub = get(); 21 | // NoPaint ret = brick::createEntity(*hub); 22 | // ret.set(PaintType::None); 23 | // return ret; 24 | // } 25 | 26 | // ColorPaint Document::createColorPaint(const ColorRGBA & _color) 27 | // { 28 | // brick::Hub * hub = get(); 29 | // ColorPaint ret = brick::createEntity(*hub); 30 | // ret.setColor(_color); 31 | // ret.set(PaintType::Color); 32 | // return ret; 33 | // } 34 | 35 | LinearGradient Document::createLinearGradient(const Vec2f & _origin, const Vec2f & _destination, 36 | const ColorStopArray & _stops) 37 | { 38 | STICK_ASSERT(Entity::hub()); 39 | LinearGradient ret = brick::createEntity(hub()); 40 | ret.set(_origin); 41 | ret.set(_destination); 42 | ret.set(_stops); 43 | ret.set(true, true); 44 | return ret; 45 | } 46 | 47 | Symbol Document::createSymbol(const Item & _item) 48 | { 49 | if (_item.parent().isValid()) 50 | _item.parent().removeChild(_item); 51 | brick::Hub * hub = get(); 52 | Symbol ret = brick::createEntity(*hub); 53 | ret.set(*this); 54 | ret.set(_item); 55 | return ret; 56 | } 57 | 58 | Group Document::createGroup(const String & _name) 59 | { 60 | brick::Hub * hub = get(); 61 | Group ret = brick::createEntity(*hub); 62 | Item::addDefaultComponents(ret, this); 63 | ret.set(_name); 64 | ret.set(EntityType::Group); 65 | ret.set(false); 66 | addChild(ret); 67 | return ret; 68 | } 69 | 70 | Path Document::createPath(const String & _name) 71 | { 72 | brick::Hub * hub = get(); 73 | Path ret = brick::createEntity(*hub); 74 | Item::addDefaultComponents(ret, this); 75 | ret.set(_name); 76 | ret.set(EntityType::Path); 77 | ret.set(SegmentArray()); 78 | ret.set(CurveArray()); 79 | ret.set(false); 80 | ret.set((comps::PathLengthData) {true, 0.0}); 81 | addChild(ret); 82 | return ret; 83 | } 84 | 85 | Path Document::createPathFromSVGData(const String & _svgData, const String _name) 86 | { 87 | Path ret = createPath(_name); 88 | svg::SVGImport::parsePathData(*this, ret, _svgData); 89 | return ret; 90 | } 91 | 92 | Path Document::createEllipse(Vec2f _center, Vec2f _size, const String & _name) 93 | { 94 | static Float s_kappa = detail::PaperConstants::kappa(); 95 | // static Vec2f s_unitSegments[12] = { Vec2f(1, 0), Vec2f(0, -s_kappa), Vec2f(0, s_kappa), 96 | // Vec2f(0, 1), Vec2f(s_kappa, 0), Vec2f(-s_kappa, 0), 97 | // Vec2f(-1, 0), Vec2f(0, s_kappa), Vec2f(0, -s_kappa), 98 | // Vec2f(0, -1), Vec2f(-s_kappa, 0), Vec2f(s_kappa, 0) 99 | // }; 100 | 101 | //Original paper values, don't conform with SVG though :( 102 | static Vec2f s_unitSegments[12] = { Vec2f(-1, 0), Vec2f(0, s_kappa), Vec2f(0, -s_kappa), 103 | Vec2f(0, -1), Vec2f(-s_kappa, 0), Vec2f(s_kappa, 0), 104 | Vec2f(1, 0), Vec2f(0, -s_kappa), Vec2f(0, s_kappa), 105 | Vec2f(0, 1), Vec2f(s_kappa, 0), Vec2f(-s_kappa, 0) 106 | }; 107 | Path ret = createPath(_name); 108 | Vec2f rad = _size * 0.5; 109 | for (Int32 i = 0; i < 4; ++i) 110 | { 111 | ret.addSegment(s_unitSegments[i * 3] * rad + _center, s_unitSegments[i * 3 + 1] * rad, s_unitSegments[i * 3 + 2] * rad); 112 | } 113 | ret.closePath(); 114 | return ret; 115 | } 116 | 117 | Path Document::createCircle(Vec2f _center, Float _radius, const String & _name) 118 | { 119 | return createEllipse(_center, Vec2f(_radius) * 2.0f, _name); 120 | } 121 | 122 | Path Document::createRectangle(Vec2f _from, Vec2f _to, const String & _name) 123 | { 124 | Path ret = createPath(_name); 125 | 126 | ret.addPoint(Vec2f(_to.x, _from.y)); 127 | ret.addPoint(_to); 128 | ret.addPoint(Vec2f(_from.x, _to.y)); 129 | ret.addPoint(_from); 130 | ret.closePath(); 131 | 132 | return ret; 133 | } 134 | 135 | void Document::setSize(Float _width, Float _height) 136 | { 137 | set(Vec2f(_width, _height)); 138 | } 139 | 140 | Float Document::width() const 141 | { 142 | return size().x; 143 | } 144 | 145 | Float Document::height() const 146 | { 147 | return size().y; 148 | } 149 | 150 | const Vec2f Document::size() const 151 | { 152 | return get(); 153 | } 154 | 155 | brick::Hub & Document::hub() 156 | { 157 | STICK_ASSERT(isValid()); 158 | STICK_ASSERT(hasComponent()); 159 | return *get(); 160 | } 161 | 162 | Allocator & Document::allocator() const 163 | { 164 | STICK_ASSERT(isValid()); 165 | STICK_ASSERT(hasComponent()); 166 | return get()->allocator(); 167 | } 168 | 169 | svg::SVGImportResult Document::parseSVG(const String & _svg, Size _dpi) 170 | { 171 | svg::SVGImport importer(*this); 172 | return importer.parse(_svg, _dpi); 173 | } 174 | 175 | svg::SVGImportResult Document::loadSVG(const String & _uri, Size _dpi) 176 | { 177 | auto result = loadTextFile(_uri); 178 | if (!result) return result.error(); 179 | return parseSVG(result.get(), _dpi); 180 | } 181 | 182 | TextResult Document::exportSVG() const 183 | { 184 | STICK_ASSERT(isValid()); 185 | svg::SVGExport exporter; 186 | return exporter.exportDocument(*this); 187 | } 188 | 189 | Error Document::saveSVG(const String & _uri) const 190 | { 191 | auto res = exportSVG(); 192 | if (res) 193 | return saveTextFile(res.get(), _uri); 194 | return res.error(); 195 | } 196 | 197 | brick::Hub & defaultHub() 198 | { 199 | static detail::DefaultPaperAllocator s_alloc; 200 | static brick::Hub s_hub(s_alloc); 201 | return s_hub; 202 | } 203 | 204 | namespace comps 205 | { 206 | using NoPaintHolder = brick::Component; 207 | } 208 | 209 | // NoPaint Document::noPaint() const 210 | // { 211 | // return get(); 212 | // } 213 | 214 | Document createDocument(brick::Hub & _hub, const String & _name) 215 | { 216 | Document doc = brick::createEntity(_hub); 217 | Item::addDefaultComponents(doc, nullptr); 218 | doc.set(_name); 219 | doc.set(EntityType::Document); 220 | doc.set(&_hub); 221 | doc.set(Vec2f(800, 600)); 222 | // doc.set(doc.createNoPaint()); 223 | //doc.set(doc.createNoPaint()); 224 | return doc; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Paper/Document.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_DOCUMENT_HPP 2 | #define PAPER_DOCUMENT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | namespace paper 22 | { 23 | class STICK_API Document : public Item 24 | { 25 | public: 26 | 27 | static constexpr EntityType itemType = EntityType::Document; 28 | 29 | Document(); 30 | 31 | template 32 | void reserveItems(stick::Size _count); 33 | 34 | // NoPaint createNoPaint(); 35 | 36 | // ColorPaint createColorPaint(const ColorRGBA & _color); 37 | 38 | LinearGradient createLinearGradient(const Vec2f & _origin, const Vec2f & _destination, 39 | const ColorStopArray & _stops = ColorStopArray()); 40 | 41 | Symbol createSymbol(const Item & _item); 42 | 43 | Group createGroup(const stick::String & _name = ""); 44 | 45 | Path createPath(const stick::String & _name = ""); 46 | 47 | Path createPathFromSVGData(const stick::String & _svgData, const stick::String _name = ""); 48 | 49 | Path createEllipse(Vec2f _center, Vec2f _size, const stick::String & _name = ""); 50 | 51 | Path createCircle(Vec2f _center, Float _radius, const stick::String & _name = ""); 52 | 53 | Path createRectangle(Vec2f _from, Vec2f _to, const stick::String & _name = ""); 54 | 55 | void setSize(Float _width, Float _height); 56 | 57 | Float width() const; 58 | 59 | Float height() const; 60 | 61 | const Vec2f size() const; 62 | 63 | // NoPaint noPaint() const; 64 | 65 | brick::Hub & hub(); 66 | 67 | svg::SVGImportResult parseSVG(const stick::String & _svg, stick::Size _dpi = 72); 68 | 69 | svg::SVGImportResult loadSVG(const stick::String & _uri, stick::Size _dpi = 72); 70 | 71 | stick::Allocator & allocator() const; 72 | 73 | stick::TextResult exportSVG() const; 74 | 75 | stick::Error saveSVG(const stick::String & _uri) const; 76 | }; 77 | 78 | STICK_API brick::Hub & defaultHub(); 79 | 80 | STICK_API Document createDocument(brick::Hub & _hub = defaultHub(), const stick::String & _name = ""); 81 | 82 | namespace detail 83 | { 84 | template 85 | static void reserveHelperImpl(brick::Hub * _hub, stick::Size _count, stick::detail::IndexSequence) 86 | { 87 | _hub->reserve::Type...>(_count); 88 | } 89 | 90 | template 91 | static void reserveHelper(brick::Hub * _hub, stick::Size _count) 92 | { 93 | detail::reserveHelperImpl(_hub, 94 | _count, 95 | stick::detail::MakeIndexSequence()); 96 | } 97 | } 98 | 99 | template 100 | void Document::reserveItems(stick::Size _count) 101 | { 102 | using MergedList = typename stick::AppendTypeList::List>::List; 103 | detail::reserveHelper(get(), _count); 104 | } 105 | } 106 | 107 | #endif //PAPER_DOCUMENT_HPP 108 | -------------------------------------------------------------------------------- /Paper/G-code/GCodeExport.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_GCODE_GCODEEXPORT_HPP 2 | #define PAPER_GCODE_GCODEEXPORT_HPP 3 | 4 | namespace paper 5 | { 6 | namespace gcode 7 | { 8 | class STICK_LOCAL GCodeExport 9 | { 10 | public: 11 | 12 | GCodeExport(); 13 | 14 | stick::TextResult exportDocument(const Document & _document); 15 | }; 16 | } 17 | } 18 | 19 | #endif //PAPER_GCODE_GCODEEXPORT_HPP 20 | -------------------------------------------------------------------------------- /Paper/Group.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace paper 5 | { 6 | Group::Group() 7 | { 8 | 9 | } 10 | 11 | void Group::setClipped(bool _b) 12 | { 13 | set(_b); 14 | } 15 | 16 | bool Group::isClipped() const 17 | { 18 | return get(); 19 | } 20 | 21 | Group Group::clone() const 22 | { 23 | return brick::reinterpretEntity(Item::clone()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Paper/Group.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_GROUP_HPP 2 | #define PAPER_GROUP_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Document; 10 | 11 | class STICK_API Group : public Item 12 | { 13 | friend class Item; 14 | 15 | public: 16 | 17 | static constexpr EntityType itemType = EntityType::Group; 18 | 19 | 20 | Group(); 21 | 22 | void setClipped(bool _b); 23 | 24 | bool isClipped() const; 25 | 26 | Group clone() const; 27 | }; 28 | } 29 | 30 | #endif //PAPER_GROUP_HPP 31 | -------------------------------------------------------------------------------- /Paper/Item.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_ITEM_HPP 2 | #define PAPER_ITEM_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Document; 10 | 11 | class Item; 12 | using ItemArray = stick::DynamicArray; 13 | 14 | class STICK_API Item : public brick::TypedEntity 15 | { 16 | public: 17 | 18 | enum class BoundsType 19 | { 20 | Fill, 21 | Stroke, 22 | Handle 23 | }; 24 | 25 | 26 | Item(); 27 | 28 | void addChild(Item _e); 29 | 30 | void insertAbove(Item _e); 31 | 32 | void insertBelow(Item _e); 33 | 34 | void sendToFront(); 35 | 36 | void sendToBack(); 37 | 38 | void remove(); 39 | 40 | bool removeChild(Item _item); 41 | 42 | void removeChildren(); 43 | 44 | void reverseChildren(); 45 | 46 | const ItemArray & children() const; 47 | 48 | const stick::String & name() const; 49 | 50 | Item parent() const; 51 | 52 | 53 | void setPosition(const Vec2f & _position); 54 | 55 | void setPivot(const Vec2f & _pivot); 56 | 57 | void setVisible(bool _b); 58 | 59 | void setName(const stick::String & _name); 60 | 61 | 62 | //transformation things 63 | 64 | void setTransform(const Mat3f & _transform, bool _bIncludesScaling = false); 65 | 66 | void translateTransform(Float _x, Float _y); 67 | 68 | void translateTransform(const Vec2f & _translation); 69 | 70 | void scaleTransform(Float _scale); 71 | 72 | void scaleTransform(Float _scaleX, Float _scaleY); 73 | 74 | void scaleTransform(const Vec2f & _scale); 75 | 76 | void scaleTransform(const Vec2f & _scale, const Vec2f & _center); 77 | 78 | void rotateTransform(Float _radians); 79 | 80 | void rotateTransform(Float _radians, const Vec2f & _point); 81 | 82 | void skewTransform(const Vec2f & _angles); 83 | 84 | void skewTransform(const Vec2f & _angles, const Vec2f & _center); 85 | 86 | void transform(const Mat3f & _transform, bool _bIncludesScaling = false); 87 | 88 | 89 | void translate(Float _x, Float _y); 90 | 91 | void translate(const Vec2f & _translation); 92 | 93 | void scale(Float _scale); 94 | 95 | void scale(Float _scaleX, Float _scaleY); 96 | 97 | void scale(const Vec2f & _scale); 98 | 99 | void scale(const Vec2f & _scale, const Vec2f & _center); 100 | 101 | void rotate(Float _radians); 102 | 103 | void rotate(Float _radians, const Vec2f & _point); 104 | 105 | void skew(const Vec2f & _angles); 106 | 107 | void skew(const Vec2f & _angles, const Vec2f & _center); 108 | 109 | void applyTransform(const Mat3f & _transform, bool _bNotifyParent = true); 110 | 111 | 112 | const Mat3f & transform() const; 113 | 114 | const Mat3f & absoluteTransform() const; 115 | 116 | stick::Float32 rotation() const; 117 | 118 | const Vec2f & translation() const; 119 | 120 | const Vec2f & scaling() const; 121 | 122 | stick::Float32 absoluteRotation() const; 123 | 124 | const Vec2f & absoluteTranslation() const; 125 | 126 | const Vec2f & absoluteScaling() const; 127 | 128 | const Rect & bounds() const; 129 | 130 | const Rect & localBounds() const; 131 | 132 | /*Rect localHandleBounds() const;*/ 133 | 134 | const Rect & handleBounds() const; 135 | 136 | const Rect & strokeBounds() const; 137 | 138 | Vec2f position() const; 139 | 140 | Vec2f pivot() const; 141 | 142 | bool isVisible() const; 143 | 144 | bool hasTransform() const; 145 | 146 | 147 | void setStrokeJoin(StrokeJoin _join); 148 | 149 | void setStrokeCap(StrokeCap _cap); 150 | 151 | void setMiterLimit(Float _limit); 152 | 153 | void setStrokeWidth(Float _width); 154 | 155 | void setStroke(const ColorRGBA & _color); 156 | 157 | void setStroke(const stick::String & _svgName); 158 | 159 | void setStroke(LinearGradient _gradient); 160 | 161 | void setDashArray(const DashArray & _arr); 162 | 163 | void setDashOffset(Float _f); 164 | 165 | void setStrokeScaling(bool _b); 166 | 167 | void setNoStroke(); 168 | 169 | void removeStroke(); 170 | 171 | void setNoFill(); 172 | 173 | void setFill(const ColorRGBA & _color); 174 | 175 | void setFill(const stick::String & _svgName); 176 | 177 | void setFill(LinearGradient _gradient); 178 | 179 | void setRemeshOnTransformChange(bool _b); 180 | 181 | void removeFill(); 182 | 183 | void setWindingRule(WindingRule _rule); 184 | 185 | StrokeJoin strokeJoin() const; 186 | 187 | StrokeCap strokeCap() const; 188 | 189 | Float fillOpacity() const; 190 | 191 | Float strokeOpacity() const; 192 | 193 | Float miterLimit() const; 194 | 195 | Float strokeWidth() const; 196 | 197 | bool remeshOnTransformChange() const; 198 | 199 | const DashArray & dashArray() const; 200 | 201 | Float dashOffset() const; 202 | 203 | WindingRule windingRule() const; 204 | 205 | bool isScalingStroke() const; 206 | 207 | Paint fill() const; 208 | 209 | Paint stroke() const; 210 | 211 | bool hasStroke() const; 212 | 213 | bool hasFill() const; 214 | 215 | //clones this item and adds it ontop of it 216 | //in the DOM. 217 | Item clone() const; 218 | 219 | Document document() const; 220 | 221 | EntityType itemType() const; 222 | 223 | 224 | void markAbsoluteTransformDirty(); 225 | 226 | void markFillGeometryDirty(); 227 | 228 | void markStrokeGeometryDirty(); 229 | 230 | void markGeometryDirty(bool _bMarkLengthDirty); 231 | 232 | void markBoundsDirty(bool _bNotifyParent); 233 | 234 | void markStrokeBoundsDirty(bool _bNotifyParent); 235 | 236 | void markFillBoundsDirty(bool _bNotifyParent); 237 | 238 | 239 | static Mat3f strokeTransform(const Mat3f * _transform, Float _strokeWidth, bool _bIsScalingStroke); 240 | 241 | 242 | template 243 | stick::Maybe findComponent() const 244 | { 245 | Item i(*this); 246 | while (i.isValid()) 247 | { 248 | auto maybe = i.maybe(); 249 | if (maybe) 250 | return maybe; 251 | i = i.parent(); 252 | } 253 | return stick::Maybe(); 254 | } 255 | 256 | static void addDefaultComponents(Item _item, Document * _doc); 257 | 258 | protected: 259 | 260 | void recursivePostTransform(bool _bIncludesScaling); 261 | 262 | void removeFromParent(); 263 | 264 | void removeImpl(bool _bRemoveFromParent); 265 | 266 | Vec2f strokePadding(Float _strokeWidth, const Mat3f & _strokeMat) const; 267 | 268 | void decomposeIfNeeded() const; 269 | 270 | void decomposeAbsoluteIfNeeded() const; 271 | 272 | struct BoundsResult 273 | { 274 | bool bEmpty; 275 | Rect rect; 276 | }; 277 | 278 | BoundsResult computeBounds(const Mat3f * _transform, BoundsType _type, bool _bAbsolute) const; 279 | }; 280 | } 281 | 282 | #endif //PAPER_ITEM_HPP 283 | -------------------------------------------------------------------------------- /Paper/OpenGL/GLRenderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_OPENGL_GLRENDERER_HPP 2 | #define PAPER_OPENGL_GLRENDERER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace paper 9 | { 10 | namespace opengl 11 | { 12 | class STICK_API GLRenderer : public RenderInterface 13 | { 14 | public: 15 | 16 | GLRenderer(); 17 | 18 | GLRenderer(const Document & _doc); 19 | 20 | ~GLRenderer(); 21 | 22 | void setViewport(Float _widthInPixels, Float _heightInPixels) override; 23 | 24 | void setTransform(const Mat3f & _transform) override; 25 | 26 | void setProjection(const Mat4f & _projection) override; 27 | 28 | void reserveItems(stick::Size _count) override; 29 | 30 | protected: 31 | 32 | using PathArray = stick::DynamicArray; 33 | using PathGeometryArray = stick::DynamicArray; 34 | struct TextureVertex 35 | { 36 | Vec2f vertex; 37 | Float textureCoord; 38 | }; 39 | using TextureGeometryArray = stick::DynamicArray; 40 | 41 | struct PathStyle 42 | { 43 | PathStyle() 44 | { 45 | //no init for speed. 46 | } 47 | 48 | PathStyle(const Item & _item) : 49 | strokeWidth(0), 50 | bHasFill(false) 51 | { 52 | bHasFill = _item.hasFill(); 53 | if (bHasFill) 54 | { 55 | fill = _item.fill(); 56 | } 57 | if (_item.hasStroke()) 58 | { 59 | stroke = _item.stroke(); 60 | strokeWidth = _item.strokeWidth(); 61 | miterLimit = _item.miterLimit(); 62 | cap = _item.strokeCap(); 63 | join = _item.strokeJoin(); 64 | dashArray = _item.dashArray(); 65 | dashOffset = _item.dashOffset(); 66 | } 67 | } 68 | 69 | bool bHasFill; 70 | Paint fill; 71 | Paint stroke; 72 | Float strokeWidth; 73 | Float miterLimit; 74 | StrokeCap cap; 75 | StrokeJoin join; 76 | DashArray dashArray; 77 | Float dashOffset; 78 | }; 79 | 80 | struct Texture 81 | { 82 | Texture(); 83 | ~Texture(); 84 | 85 | stick::UInt32 glTexture; 86 | }; 87 | 88 | struct GradientCacheData 89 | { 90 | Texture texture; 91 | }; 92 | 93 | struct RenderCacheData 94 | { 95 | PathGeometryArray fillVertices; 96 | stick::DynamicArray joins; 97 | PathGeometryArray boundsVertices; 98 | PathGeometryArray strokeVertices; 99 | PathGeometryArray strokeBoundsVertices; 100 | crunch::Mat4f transformProjection; 101 | stick::UInt32 strokeVertexDrawMode; 102 | TextureGeometryArray textureVertices; 103 | }; 104 | 105 | using RenderCache = brick::Component; 106 | using GradientCache = brick::Component; 107 | 108 | //these have to be implemented 109 | stick::Error drawPath(const Path & _path, const Mat3f * _transform) override; 110 | stick::Error beginClipping(const Path & _clippingPath, const Mat3f * _transform) override; 111 | stick::Error endClipping(const Path & _clippingPath, const Mat3f * _transform) override; 112 | 113 | stick::Error drawPathImpl(const Path & _path, bool _bIsClippingPath, const Mat3f * _transform); 114 | stick::Error generateClippingMask(const Path & _clippingPath, bool _bIsRebuilding, const Mat3f * _transform); 115 | 116 | //these can be implemented 117 | stick::Error prepareDrawing() override; 118 | stick::Error finishDrawing() override; 119 | 120 | RenderCacheData & updateRenderCache(const Path & _path, const PathStyle & _style, bool _bIsClipping); 121 | 122 | RenderCacheData & recursivelyDrawEvenOddPath(const Path & _path, const Mat4f * _tp, 123 | const PathStyle & _style, bool _bIsClipping); 124 | 125 | RenderCacheData & recursivelyDrawNonZeroPath(const Path & _path, const Mat4f * _tp, 126 | const PathStyle & _style, bool _bIsClipping); 127 | 128 | stick::Error recursivelyDrawStroke(const Path & _path, const Mat4f * _tp, 129 | const PathStyle & _style, stick::UInt32 _clippingPlaneToTestAgainst); 130 | 131 | stick::Error drawFillEvenOdd(const Path & _path, 132 | const crunch::Mat4f * _tp, 133 | stick::UInt32 _targetStencilBufferMask, 134 | stick::UInt32 _clippingPlaneToTestAgainst, 135 | const PathStyle & _style, 136 | bool _bIsClippingPath); 137 | 138 | stick::Error drawFillNonZero(const Path & _path, 139 | const crunch::Mat4f * _tp, 140 | stick::UInt32 _targetStencilBufferMask, 141 | stick::UInt32 _clippingPlaneToTestAgainst, 142 | const PathStyle & _style, 143 | bool _bIsClippingPath); 144 | 145 | 146 | stick::Error drawStroke(const Path & _path, const Mat4f * _tp, const PathStyle & _style, stick::UInt32 _clippingPlaneToTestAgainst); 147 | 148 | 149 | stick::Error drawFilling(const Path & _path, 150 | RenderCacheData & _cache, 151 | const crunch::Mat4f * _tp, 152 | const PathStyle & _style, 153 | bool _bStroke); 154 | 155 | 156 | struct StencilPlanes 157 | { 158 | stick::UInt32 targetStencilMask; 159 | stick::UInt32 clippingPlaneToTestAgainst; 160 | }; 161 | StencilPlanes prepareStencilPlanes(bool _bIsClippingPath); 162 | 163 | stick::UInt32 m_program; 164 | stick::UInt32 m_programTexture; 165 | stick::UInt32 m_vao; 166 | stick::UInt32 m_vaoTexture; 167 | stick::UInt32 m_vbo; 168 | stick::UInt32 m_vboTexture; 169 | stick::UInt32 m_currentClipStencilPlane; 170 | stick::DynamicArray m_clippingMaskStack; 171 | bool m_bIsInitialized; 172 | bool m_bCanSwapStencilPlanesWhenEnding; 173 | bool m_bIsClipping; 174 | Vec2f m_viewport; 175 | Mat4f m_projection; 176 | bool m_bHasCustomProjection; 177 | Mat4f m_transform; 178 | Mat4f m_transformProjection; 179 | Vec2f m_transformScale; 180 | bool m_bTransformScaleChanged; 181 | }; 182 | } 183 | } 184 | 185 | #endif //PAPER_OPENGL_GLRENDERER_HPP 186 | -------------------------------------------------------------------------------- /Paper/Paint.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace paper 4 | { 5 | // Paint Paint::clone() const 6 | // { 7 | // return cloneImpl(); 8 | // } 9 | 10 | // PaintType Paint::paintType() const 11 | // { 12 | // STICK_ASSERT(isValid()); 13 | // return get(); 14 | // } 15 | 16 | // Paint Paint::cloneImpl() const 17 | // { 18 | // return brick::reinterpretEntity(Entity::clone()); 19 | // } 20 | 21 | // void Paint::remove() 22 | // { 23 | // Entity::destroy(); 24 | // } 25 | 26 | // NoPaint NoPaint::clone() const 27 | // { 28 | // return brick::reinterpretEntity(Paint::clone()); 29 | // } 30 | 31 | // void ColorPaint::setColor(const ColorRGBA & _color) 32 | // { 33 | // STICK_ASSERT(isValid()); 34 | // set(_color); 35 | // } 36 | 37 | // const ColorRGBA & ColorPaint::color() const 38 | // { 39 | // STICK_ASSERT(isValid()); 40 | // return get(); 41 | // } 42 | 43 | // ColorPaint ColorPaint::clone() const 44 | // { 45 | // return brick::reinterpretEntity(Paint::clone()); 46 | // } 47 | 48 | void BaseGradient::setOrigin(const Vec2f & _position) 49 | { 50 | set(_position); 51 | markPositionsDirty(); 52 | } 53 | 54 | void BaseGradient::setDestination(const Vec2f & _position) 55 | { 56 | set(_position); 57 | markPositionsDirty(); 58 | } 59 | 60 | void BaseGradient::setOriginAndDestination(const Vec2f & _orig, const Vec2f & _dest) 61 | { 62 | set(_orig); 63 | set(_dest); 64 | markPositionsDirty(); 65 | } 66 | 67 | void BaseGradient::addStop(const ColorRGBA & _color, Float _offset) 68 | { 69 | get().append({_color, _offset}); 70 | markStopsDirty(); 71 | } 72 | 73 | const Vec2f & BaseGradient::origin() const 74 | { 75 | return get(); 76 | } 77 | 78 | const Vec2f & BaseGradient::destination() const 79 | { 80 | return get(); 81 | } 82 | 83 | const ColorStopArray & BaseGradient::stops() const 84 | { 85 | return get(); 86 | } 87 | 88 | void BaseGradient::markStopsDirty() 89 | { 90 | get().bStopsDirty = true; 91 | } 92 | 93 | void BaseGradient::markPositionsDirty() 94 | { 95 | get().bPositionsDirty = true; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /Paper/Paint.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PAINT_HPP 2 | #define PAPER_PAINT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace paper 11 | { 12 | // @TODO: Reconsider if paints should be entities or simple self contained types: 13 | // The advantage of having them as entities is that it fits quite nicely into the 14 | // workflow/api of the rest of the codebase. 15 | // 16 | // The disadvantage is that the underlying Hub will allocate memory for all components 17 | // for all entities. That might be a lot of memory...we shall see. 18 | // 19 | // To avoid this we could either create self contained types and possibly say that 20 | // all paints are value types (the amount of data in them should be pretty small), or 21 | // use some different kind of shared ownership (i.e. some sort of shared_ptr). 22 | // 23 | // Another alternative would be to come up with some form of entity category mechanism 24 | // in brick to avoid that all components are allocated for all entities but rather on 25 | // a per category basis. 26 | 27 | // class STICK_API Paint : public brick::SharedTypedEntity 28 | // { 29 | // public: 30 | 31 | // Paint clone() const; 32 | 33 | // PaintType paintType() const; 34 | 35 | // void remove(); 36 | 37 | // private: 38 | 39 | // virtual Paint cloneImpl() const; 40 | // }; 41 | 42 | // class STICK_API NoPaint : public Paint 43 | // { 44 | // public: 45 | 46 | // static constexpr PaintType paintType = PaintType::None; 47 | 48 | // NoPaint clone() const; 49 | // }; 50 | 51 | // class STICK_API ColorPaint : public Paint 52 | // { 53 | // public: 54 | 55 | // static constexpr PaintType paintType = PaintType::Color; 56 | 57 | 58 | // void setColor(const ColorRGBA & _color); 59 | 60 | // const ColorRGBA & color() const; 61 | 62 | // ColorPaint clone() const; 63 | // }; 64 | 65 | struct STICK_API ColorStop 66 | { 67 | ColorRGBA color; 68 | Float offset; 69 | }; 70 | 71 | using ColorStopArray = stick::DynamicArray; 72 | 73 | 74 | namespace comps 75 | { 76 | using Origin = brick::Component; 77 | using Destination = brick::Component; 78 | using ColorStops = brick::Component; 79 | struct STICK_LOCAL GradientDirtyFlagsData 80 | { 81 | bool bStopsDirty; 82 | bool bPositionsDirty; 83 | }; 84 | using GradientDirtyFlags = brick::Component; 85 | } 86 | 87 | class STICK_API BaseGradient : public brick::SharedTypedEntity 88 | { 89 | public: 90 | 91 | void setOrigin(const Vec2f & _position); 92 | 93 | void setDestination(const Vec2f & _position); 94 | 95 | void setOriginAndDestination(const Vec2f & _orig, const Vec2f & _dest); 96 | 97 | void addStop(const ColorRGBA & _color, Float _offset); 98 | 99 | 100 | const Vec2f & origin() const; 101 | 102 | const Vec2f & destination() const; 103 | 104 | const ColorStopArray & stops() const; 105 | 106 | protected: 107 | 108 | void markStopsDirty(); 109 | 110 | void markPositionsDirty(); 111 | }; 112 | 113 | class STICK_API LinearGradient : public BaseGradient 114 | { 115 | }; 116 | 117 | class STICK_API RadialGradient : public BaseGradient 118 | { 119 | }; 120 | 121 | struct STICK_API NoPaint {}; 122 | using Paint = stick::Variant; 123 | } 124 | 125 | #endif //PAPER_PAINT_HPP 126 | -------------------------------------------------------------------------------- /Paper/Path.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PATH_HPP 2 | #define PAPER_PATH_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace paper 9 | { 10 | class CurveLocation; 11 | struct Intersection; 12 | 13 | using IntersectionArray = stick::DynamicArray; 14 | 15 | //@TODO: Remove the friend PathFitter eventually and this forward decl 16 | namespace detail 17 | { 18 | class PathFitter; 19 | } 20 | 21 | class STICK_API Path : public Item 22 | { 23 | friend class Curve; 24 | friend class Segment; 25 | friend class Item; 26 | friend class detail::PathFitter; 27 | 28 | public: 29 | 30 | static constexpr EntityType itemType = EntityType::Path; 31 | 32 | using SegmentView = detail::ContainerView; 33 | using SegmentViewConst = detail::ContainerView; 34 | using CurveView = detail::ContainerView; 35 | using CurveViewConst = detail::ContainerView; 36 | 37 | Path(); 38 | 39 | //absolute post-script style drawing commands 40 | void addPoint(const Vec2f & _to); 41 | 42 | void cubicCurveTo(const Vec2f & _handleOne, const Vec2f & _handleTwo, const Vec2f & _to); 43 | 44 | void quadraticCurveTo(const Vec2f & _handle, const Vec2f & _to); 45 | 46 | void curveTo(const Vec2f & _through, const Vec2f & _to, stick::Float32 _parameter = 0.5); 47 | 48 | stick::Error arcTo(const Vec2f & _through, const Vec2f & _to); 49 | 50 | stick::Error arcTo(const Vec2f & _to, bool _bClockwise = true); 51 | 52 | stick::Error arcTo(const Vec2f & _to, const Vec2f & _radii, Float _rotation, bool _bClockwise, bool _bLarge); 53 | 54 | 55 | //relative post-script style drawing commands 56 | void cubicCurveBy(const Vec2f & _handleOne, const Vec2f & _handleTwo, const Vec2f & _by); 57 | 58 | void quadraticCurveBy(const Vec2f & _handle, const Vec2f & _by); 59 | 60 | void curveBy(const Vec2f & _through, const Vec2f & _by, stick::Float32 _parameter = 0.5); 61 | 62 | stick::Error arcBy(const Vec2f & _through, const Vec2f & _by); 63 | 64 | stick::Error arcBy(const Vec2f & _to, bool _bClockwise = true); 65 | 66 | 67 | void closePath(); 68 | 69 | //TODO: Add the different smoothing versions / algorithms from more recent paper.js versions 70 | void smooth(Smoothing _type = Smoothing::Asymmetric); 71 | 72 | void smooth(stick::Int64 _from, stick::Int64 _to, Smoothing _type = Smoothing::Asymmetric); 73 | 74 | void simplify(Float _tolerance = 2.5); 75 | 76 | 77 | void addSegment(const Vec2f & _point, const Vec2f & _handleIn, const Vec2f & _handleOut); 78 | 79 | Segment & insertSegment(stick::Size _index, const Vec2f & _point, 80 | const Vec2f & _handleIn = Vec2f(0.0), 81 | const Vec2f & _handleOut = Vec2f(0.0)); 82 | 83 | void removeSegment(stick::Size _index); 84 | 85 | void removeSegments(stick::Size _from); 86 | 87 | void removeSegments(stick::Size _from, stick::Size _to); 88 | 89 | void removeSegments(); 90 | 91 | SegmentViewConst segments() const; 92 | 93 | SegmentView segments(); 94 | 95 | CurveViewConst curves() const; 96 | 97 | CurveView curves(); 98 | 99 | 100 | Vec2f positionAt(Float _offset) const; 101 | 102 | Vec2f normalAt(Float _offset) const; 103 | 104 | Vec2f tangentAt(Float _offset) const; 105 | 106 | Float curvatureAt(Float _offset) const; 107 | 108 | Float angleAt(Float _offset) const; 109 | 110 | void reverse(); 111 | 112 | void setClockwise(bool _b); 113 | 114 | Curve & curve(stick::Size _index); 115 | 116 | const Curve & curve(stick::Size _index) const; 117 | 118 | Segment & segment(stick::Size _index); 119 | 120 | const Segment & segment(stick::Size _index) const; 121 | 122 | stick::Size curveCount() const; 123 | 124 | stick::Size segmentCount() const; 125 | 126 | //NOTE: This function is slightly different from paper.js 127 | //The paper.js version takes a maxDistance and spaces the resulting 128 | //segments evenly based on that max distance. This version takes a minDistance 129 | //which the segments try to keep at least, and spaces the segments non linearly 130 | //to match the original path as closesly as possible (i.e. linear parts of the path, 131 | //are subdivided only very little while curvy areas are subdivided a lot) 132 | void flatten(Float _angleTolerance = 0.25, Float _minDistance = 0.0, stick::Size _maxRecursion = 32); 133 | 134 | void flattenRegular(Float _maxDistance); 135 | 136 | struct OffsetAndSampleCount 137 | { 138 | Float offset; 139 | stick::Size sampleCount; 140 | }; 141 | 142 | OffsetAndSampleCount regularOffsetAndSampleCount(Float _maxDistance); 143 | 144 | Float regularOffset(Float _maxDistance); 145 | 146 | Path splitAt(Float _offset); 147 | 148 | Path splitAt(const CurveLocation & _loc); 149 | 150 | Path slice(Float _from, Float _to) const; 151 | 152 | Path slice(const CurveLocation & _from, const CurveLocation & _to) const; 153 | 154 | CurveLocation closestCurveLocation(const Vec2f & _point, Float & _outDistance) const; 155 | 156 | CurveLocation closestCurveLocation(const Vec2f & _point) const; 157 | 158 | CurveLocation curveLocationAt(Float _offset) const; 159 | 160 | void peaks(stick::DynamicArray & _peaks) const; 161 | 162 | void extrema(stick::DynamicArray & _extrema) const; 163 | 164 | stick::DynamicArray extrema() const; 165 | 166 | 167 | Float length() const; 168 | 169 | Float area() const; 170 | 171 | 172 | bool isClosed() const; 173 | 174 | bool isPolygon() const; 175 | 176 | bool isClockwise() const; 177 | 178 | bool contains(const Vec2f & _p) const; 179 | 180 | Path clone() const; 181 | 182 | SegmentArray & segmentArray(); 183 | 184 | CurveArray & curveArray(); 185 | 186 | const CurveArray & curveArray() const; 187 | 188 | const SegmentArray & segmentArray() const; 189 | 190 | 191 | IntersectionArray intersections() const; 192 | 193 | IntersectionArray intersections(const Path & _other) const; 194 | 195 | 196 | private: 197 | 198 | Segment & createSegment(const Vec2f & _pos, const Vec2f & _handleIn, const Vec2f & _handleOut); 199 | 200 | IntersectionArray intersectionsImpl(const Path & _other) const; 201 | 202 | 203 | //called from Segment 204 | void segmentChanged(const Segment & _seg); 205 | 206 | void rebuildCurves(); 207 | 208 | void updateSegmentIndices(stick::Size _from, stick::Size _to); 209 | 210 | BoundsResult computeBoundsImpl(Float _padding, const Mat3f * _transform); 211 | 212 | BoundsResult computeBounds(const Mat3f * _transform); 213 | 214 | BoundsResult computeHandleBounds(const Mat3f * _transform); 215 | 216 | BoundsResult computeStrokeBounds(const Mat3f * _transform); 217 | 218 | stick::Error arcHelper(Float _extentDeg, Segment & _segment, const Vec2f & _direction, const Vec2f & _to, 219 | const Vec2f & _center, const Mat3f * _transform); 220 | 221 | void applyTransform(const Mat3f & _transform); 222 | }; 223 | } 224 | 225 | #endif //PAPER_PATH_HPP 226 | -------------------------------------------------------------------------------- /Paper/PlacedSymbol.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | namespace paper 5 | { 6 | PlacedSymbol::PlacedSymbol() 7 | { 8 | 9 | } 10 | 11 | Symbol PlacedSymbol::symbol() const 12 | { 13 | STICK_ASSERT(isValid()); 14 | return get(); 15 | } 16 | 17 | void PlacedSymbol::remove() 18 | { 19 | STICK_ASSERT(isValid()); 20 | Symbol & s = get(); 21 | if (s.isValid()) 22 | { 23 | auto & ps = s.get(); 24 | auto it = stick::find(ps.begin(), ps.end(), *this); 25 | if (it != ps.end()) 26 | ps.remove(it); 27 | } 28 | Item::remove(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Paper/PlacedSymbol.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PLACEDSYMBOL_HPP 2 | #define PAPER_PLACEDSYMBOL_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Symbol; 10 | 11 | namespace comps 12 | { 13 | using ReferencedSymbol = brick::Component; 14 | } 15 | 16 | class STICK_API PlacedSymbol : public Item 17 | { 18 | public: 19 | 20 | static constexpr EntityType itemType = EntityType::PlacedSymbol; 21 | 22 | 23 | PlacedSymbol(); 24 | 25 | Symbol symbol() const; 26 | 27 | void remove(); 28 | }; 29 | } 30 | 31 | #endif //PAPER_PLACEDSYMBOL_HPP 32 | -------------------------------------------------------------------------------- /Paper/Private/Allocator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_ALLOCATOR_HPP 2 | #define PAPER_PRIVATE_ALLOCATOR_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace paper 14 | { 15 | namespace detail 16 | { 17 | // using MainAllocator = stick::mem::GlobalAllocator < 18 | // stick::mem::FallbackAllocator, 19 | // stick::mem::Mallocator> 20 | // >; 21 | 22 | using MainAllocator = stick::mem::GlobalAllocator < 23 | stick::mem::Mallocator 24 | >; 25 | 26 | template 27 | using PoolAllocator = stick::mem::PoolAllocator; 28 | using SmallAllocator = stick::mem::PoolAllocator; 29 | 30 | using PaperAllocator = stick::mem::Segregator < 31 | stick::mem::T<8>, SmallAllocator, 32 | stick::mem::T<128>, stick::mem::Bucketizer, 1, 128, 16>, 33 | stick::mem::T<256>, stick::mem::Bucketizer, 129, 256, 32>, 34 | stick::mem::T<512>, stick::mem::Bucketizer, 257, 512, 64>, 35 | stick::mem::T<1024>, stick::mem::Bucketizer, 513, 1024, 128>, 36 | stick::mem::T<2048>, stick::mem::Bucketizer, 1025, 2048, 256>, 37 | stick::mem::T<4096>, stick::mem::Bucketizer, 2049, 4096, 512>, 38 | MainAllocator>; 39 | 40 | class STICK_API DefaultPaperAllocator : public stick::Allocator 41 | { 42 | public: 43 | 44 | inline stick::mem::Block allocate(stick::Size _byteCount, stick::Size _alignment) override 45 | { 46 | return m_alloc.allocate(_byteCount, _alignment); 47 | } 48 | 49 | inline void deallocate(const stick::mem::Block & _block) override 50 | { 51 | m_alloc.deallocate(_block); 52 | } 53 | 54 | private: 55 | 56 | PaperAllocator m_alloc; 57 | }; 58 | } 59 | } 60 | 61 | #endif //PAPER_PRIVATE_ALLOCATOR_HPP 62 | -------------------------------------------------------------------------------- /Paper/Private/BooleanOperations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | namespace paper 10 | { 11 | namespace detail 12 | { 13 | using namespace stick; 14 | 15 | static void insertCurve(const Bezier & _c, MonoCurveLoop & _target) 16 | { 17 | Float y0 = _c.positionOne().y; 18 | Float y1 = _c.positionTwo().y; 19 | 20 | Int32 winding; 21 | if (crunch::abs((y0 - y1) / (_c.positionOne().x - _c.positionTwo().x)) < detail::PaperConstants::geometricEpsilon()) 22 | { 23 | winding = 0; 24 | } 25 | else if (y0 > y1) 26 | { 27 | winding = -1; 28 | } 29 | else 30 | { 31 | winding = 1; 32 | } 33 | 34 | MonoCurve c = {_c, winding}; 35 | _target.monoCurves.append(c); 36 | 37 | // Keep track of the last non-horizontal curve (with winding). 38 | if (winding) 39 | _target.last = c; 40 | } 41 | 42 | static void handleCurve(const Bezier & _c, MonoCurveLoop & _target) 43 | { 44 | // Filter out curves of zero length. 45 | // TODO: Do not filter this here. 46 | if (_c.length() == 0) 47 | { 48 | return; 49 | } 50 | 51 | Float y0, y1, y2, y3; 52 | y0 = _c.positionOne().y; 53 | y1 = _c.handleOne().y; 54 | y2 = _c.handleTwo().y; 55 | y3 = _c.positionTwo().y; 56 | 57 | if (_c.isStraight() || (y0 >= y1 == y1 >= y2 && y1 >= y2 == y2 >= y3)) 58 | { 59 | // Straight curves and curves with end and control points sorted 60 | // in y direction are guaranteed to be monotonic in y direction. 61 | insertCurve(_c, _target); 62 | } 63 | else 64 | { 65 | // Split the curve at y extrema, to get bezier curves with clear 66 | // orientation: Calculate the derivative and find its roots. 67 | 68 | Float a = (y1 - y2) * 3.0 - y0 + y3; 69 | Float b = (y0 + y2) * 2.0 - y1 * 4.0; 70 | Float c = y1 - y0; 71 | 72 | Float tMin = detail::PaperConstants::curveTimeEpsilon(); 73 | Float tMax = 1 - tMin; 74 | 75 | // Keep then range to 0 .. 1 (excluding) in the search for y 76 | // extrema. 77 | auto res = crunch::solveQuadratic(a, b, c, tMin, tMax); 78 | if (res.count == 0) 79 | { 80 | insertCurve(_c, _target); 81 | } 82 | else 83 | { 84 | std::sort(&res.values[0], &res.values[0] + res.count); 85 | Float t = res.values[0]; 86 | 87 | auto curves = _c.subdivide(t); 88 | insertCurve(curves.first, _target); 89 | if (res.count > 1) 90 | { 91 | // If there are two extrema, renormalize t to the range 92 | // of the second range and split again. 93 | t = (res.values[1] - t) / (1 - t); 94 | // Since we already processed curves.first, we can override 95 | // the parts array with the new pair now. 96 | curves = curves.second.subdivide(t); 97 | insertCurve(curves.first, _target); 98 | } 99 | insertCurve(curves.second, _target); 100 | } 101 | } 102 | } 103 | 104 | const MonoCurveLoopArray & monoCurves(Path & _path) 105 | { 106 | if (!_path.hasComponent()) 107 | { 108 | MonoCurveLoop data; 109 | if (_path.absoluteTransform() != Mat3f::identity()) 110 | { 111 | data.bTransformed = true; 112 | data.inverseTransform = crunch::inverse(_path.absoluteTransform()); 113 | } 114 | else 115 | data.bTransformed = false; 116 | 117 | const CurveArray & curves = _path.curveArray(); 118 | for (auto & c : curves) 119 | handleCurve(c->bezier(), data); 120 | 121 | // If the path is not closed, we need to join the end points with a 122 | // straight line, just like how filling open paths works. 123 | 124 | if (!_path.isClosed() && _path.segmentArray().count() > 1) 125 | { 126 | auto & segs = _path.segmentArray(); 127 | Bezier tmp(segs.last()->position(), segs.last()->position(), 128 | segs.first()->position(), segs.first()->position()); 129 | handleCurve(tmp, data); 130 | } 131 | 132 | MonoCurveLoopArray loops; 133 | loops.append(data); 134 | 135 | // If this is a compound path, get the child mono curves and append them 136 | for (const Item & c : _path.children()) 137 | { 138 | Path p = brick::reinterpretEntity(c); 139 | const MonoCurveLoopArray & cc = monoCurves(p); 140 | 141 | // technically nested compound paths are not supported right now 142 | // (i.e. a compound path only draws its own children, not recursively at this point) 143 | // hence, to avoid confusion, we explicitly only append the first Loop of the child 144 | STICK_ASSERT(cc.count() == 1); 145 | loops.append(cc[0]); 146 | 147 | // we don't want to cache the curves twice. 148 | p.removeComponent(); 149 | } 150 | 151 | _path.set(loops); 152 | } 153 | 154 | return _path.get(); 155 | } 156 | 157 | //@TODO: Update this to the changed implementation (apparently more stable) from paper.js develop 158 | Int32 winding(const Vec2f & _point, const MonoCurveLoopArray & _loops, bool _bHorizontal) 159 | { 160 | Float epsilon = detail::PaperConstants::windingEpsilon(); 161 | Int32 windingLeft = 0; 162 | Int32 windingRight = 0; 163 | 164 | // Horizontal curves may return wrong results, since the curves are 165 | // monotonic in y direction and this is an indeterminate state. 166 | if (_bHorizontal) 167 | { 168 | Float yTop = -std::numeric_limits::infinity(); 169 | Float yBottom = std::numeric_limits::infinity(); 170 | Float yBefore; 171 | Float yAfter; 172 | 173 | for (const MonoCurveLoop & loop : _loops) 174 | { 175 | Vec2f p = loop.bTransformed ? loop.inverseTransform * _point : _point; 176 | yBefore = p.y - epsilon; 177 | yAfter = p.y + epsilon; 178 | // Find the closest top and bottom intercepts for the vertical line. 179 | for (const MonoCurve & c : loop.monoCurves) 180 | { 181 | auto result = c.bezier.solveCubic(p.x, true, 0, 1); 182 | for (Int32 j = result.count - 1; j >= 0; j--) 183 | { 184 | Float y = c.bezier.positionAt(result.values[j]).y; 185 | if (y < yBefore && y > yTop) 186 | { 187 | yTop = y; 188 | } 189 | else if (y > yAfter && y < yBottom) 190 | { 191 | yBottom = y; 192 | } 193 | } 194 | } 195 | } 196 | 197 | // Shift the point lying on the horizontal curves by half of the 198 | // closest top and bottom intercepts. 199 | yTop = (yTop + _point.y) * 0.5; 200 | yBottom = (yBottom + _point.y) * 0.5; 201 | if (yTop > -std::numeric_limits::max()) 202 | windingLeft = winding(Vec2f(_point.x, yTop), _loops, false); 203 | if (yBottom < std::numeric_limits::max()) 204 | windingRight = winding(Vec2f(_point.x, yBottom), _loops, false); 205 | } 206 | else 207 | { 208 | Float xBefore; 209 | Float xAfter; 210 | Int32 prevWinding; 211 | Float prevXEnd; 212 | bool bIsOnCurve = false; 213 | // Separately count the windings for points on curves. 214 | Int32 windLeftOnCurve = 0; 215 | Int32 windRightOnCurve = 0; 216 | 217 | for (const MonoCurveLoop & loop : _loops) 218 | { 219 | Vec2f p = loop.bTransformed ? loop.inverseTransform * _point : _point; 220 | xBefore = p.x - epsilon; 221 | xAfter = p.x + epsilon; 222 | for (Size i = 0; i < loop.monoCurves.count(); ++i) 223 | { 224 | const MonoCurve & curve = loop.monoCurves[i]; 225 | Float yStart = curve.bezier.positionOne().y; 226 | Float yEnd = curve.bezier.positionTwo().y; 227 | Int32 winding = curve.winding; 228 | 229 | // The first curve of a loop holds the last curve with non-zero 230 | // winding. Retrieve and use it here. 231 | if (i == 0) 232 | { 233 | prevWinding = loop.last.winding; 234 | prevXEnd = loop.last.bezier.positionTwo().x; 235 | // Reset the on curve flag for each loop. 236 | bIsOnCurve = false; 237 | } 238 | 239 | // Since the curves are monotonic in y direction, we can just 240 | // compare the endpoints of the curve to determine if the ray 241 | // from query point along +-x direction will intersect the 242 | // monotonic curve. 243 | if ((p.y >= yStart && p.y <= yEnd) || (p.y >= yEnd && p.y <= yStart)) 244 | { 245 | if (winding != 0) 246 | { 247 | // Calculate the x value for the ray's intersection. 248 | Float x; 249 | bool bGotX = true; 250 | if (p.y == yStart) 251 | { 252 | x = curve.bezier.positionOne().x; 253 | } 254 | else if (p.y == yEnd) 255 | { 256 | x = curve.bezier.positionTwo().x; 257 | } 258 | else 259 | { 260 | auto roots = curve.bezier.solveCubic(p.y, false, 0, 1); 261 | if (roots.count == 1) 262 | { 263 | x = curve.bezier.positionAt(roots.values[0]).x; 264 | } 265 | else 266 | { 267 | bGotX = false; 268 | } 269 | } 270 | 271 | if (bGotX) 272 | { 273 | // Test if the point is on the current mono-curve. 274 | if (x >= xBefore && x <= xAfter) 275 | { 276 | bIsOnCurve = true; 277 | } 278 | else if ( 279 | // Count the intersection of the ray with the 280 | // monotonic curve if the crossing is not the 281 | // start of the curve, except if the winding 282 | // changes... 283 | (p.y != yStart || winding != prevWinding) 284 | // ...and the point is not on the curve or on 285 | // the horizontal connection between the last 286 | // non-horizontal curve's end point and the 287 | // current curve's start point. 288 | && !(p.y == yStart 289 | && (p.x - x) * (p.x - prevXEnd) < 0)) 290 | { 291 | if (x < xBefore) 292 | { 293 | windingLeft += winding; 294 | } 295 | else if (x > xAfter) 296 | { 297 | windingRight += winding; 298 | } 299 | } 300 | } 301 | 302 | // Update previous winding and end coordinate whenever 303 | // the ray intersects a non-horizontal curve. 304 | prevWinding = winding; 305 | prevXEnd = curve.bezier.positionTwo().x; 306 | } 307 | // Test if the point is on the horizontal curve. 308 | else if ((p.x - curve.bezier.positionOne().x) * (p.x - curve.bezier.positionTwo().x) <= 0) 309 | { 310 | bIsOnCurve = true; 311 | } 312 | } 313 | 314 | // If we are at the end of a loop and the point was on a curve 315 | // of the loop, we increment / decrement the on-curve winding 316 | // numbers as if the point was inside the path. 317 | if (bIsOnCurve && (i >= loop.monoCurves.count() - 1)) 318 | { 319 | //printf("ON CURVE WIND\n"); 320 | windLeftOnCurve += 1; 321 | windRightOnCurve -= 1; 322 | } 323 | } 324 | } 325 | 326 | // Use the on-curve windings if no other intersections were found or 327 | // if they canceled each other. On single paths this ensures that 328 | // the overall winding is 1 if the point was on a monotonic curve. 329 | if (windingLeft == 0 && windingRight == 0) 330 | { 331 | //printf("USE ON CURVE WIND\n"); 332 | windingLeft = windLeftOnCurve; 333 | windingRight = windRightOnCurve; 334 | } 335 | } 336 | 337 | return max(abs(windingLeft), abs(windingRight)); 338 | } 339 | 340 | } 341 | } 342 | -------------------------------------------------------------------------------- /Paper/Private/BooleanOperations.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_BOOLEANOPERATIONS_HPP 2 | #define PAPER_PRIVATE_BOOLEANOPERATIONS_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class Path; 9 | 10 | namespace detail 11 | { 12 | struct STICK_LOCAL MonoCurve 13 | { 14 | Bezier bezier; 15 | stick::Int32 winding; 16 | }; 17 | 18 | using MonoCurveArray = stick::DynamicArray; 19 | 20 | struct STICK_LOCAL MonoCurveLoop 21 | { 22 | Mat3f inverseTransform; 23 | bool bTransformed; 24 | MonoCurveArray monoCurves; 25 | MonoCurve last; 26 | }; 27 | 28 | using MonoCurveLoopArray = stick::DynamicArray; 29 | 30 | namespace comps 31 | { 32 | using MonoCurves = brick::Component; 33 | } 34 | 35 | STICK_LOCAL const MonoCurveLoopArray & monoCurves(Path & _path); 36 | 37 | STICK_LOCAL stick::Int32 winding(const Vec2f & _point, const MonoCurveLoopArray & _loops, bool _bHorizontal); 38 | } 39 | } 40 | 41 | #endif //PAPER_PRIVATE_BOOLEANOPERATIONS_HPP 42 | -------------------------------------------------------------------------------- /Paper/Private/ContainerView.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_CONTAINERVIEW_HPP 2 | #define PAPER_PRIVATE_CONTAINERVIEW_HPP 3 | 4 | namespace paper 5 | { 6 | namespace detail 7 | { 8 | template 9 | struct Transformer; 10 | 11 | template 12 | struct Transformer, T> 13 | { 14 | static T & transform(const stick::UniquePtr & _from) 15 | { 16 | return *_from; 17 | }; 18 | }; 19 | 20 | template 21 | struct ContainerViewTraits; 22 | 23 | template 24 | struct ContainerViewTraits 25 | { 26 | using IterType = ConstIter; 27 | using ValueType = const TO; 28 | }; 29 | 30 | template 31 | struct ContainerViewTraits 32 | { 33 | using IterType = Iter; 34 | using ValueType = TO; 35 | }; 36 | 37 | template 38 | class ContainerView 39 | { 40 | public: 41 | using ContainerType = CT; 42 | using ContainerIter = typename CT::Iter; 43 | using ContainerConstIter = typename CT::ConstIter; 44 | using ContainerItemType = typename CT::ValueType; 45 | using Traits = ContainerViewTraits; 46 | using ValueType = typename Traits::ValueType; 47 | using PointerType = ValueType*; 48 | using ReferenceType = ValueType&; 49 | using InternalIter = typename Traits::IterType; 50 | 51 | template 52 | struct IterT 53 | { 54 | using ValueType = VT; 55 | using ReferenceType = ContainerView::ReferenceType; 56 | using PointerType = ContainerView::PointerType; 57 | 58 | IterT() 59 | { 60 | 61 | } 62 | 63 | IterT(const InternalIter & _it) : 64 | m_it(_it) 65 | { 66 | 67 | } 68 | 69 | IterT(const IterT & _other) : 70 | m_it(_other.m_it) 71 | { 72 | 73 | } 74 | 75 | bool operator == (const IterT & _other) const 76 | { 77 | return m_it == _other.m_it; 78 | } 79 | 80 | bool operator != (const IterT & _other) const 81 | { 82 | return m_it != _other.m_it; 83 | } 84 | 85 | ValueType & operator* () const 86 | { 87 | return Transformer::transform(*m_it); 88 | } 89 | 90 | IterT & operator++() 91 | { 92 | m_it++; 93 | return *this; 94 | } 95 | 96 | IterT operator++(int) 97 | { 98 | IterT ret(*this); 99 | ++(*this); 100 | return ret; 101 | } 102 | 103 | InternalIter m_it; 104 | }; 105 | 106 | using Iter = IterT; 107 | //using ConstIter = IterT; 108 | 109 | 110 | ContainerView() 111 | { 112 | } 113 | 114 | ContainerView(InternalIter _begin, InternalIter _end) : 115 | m_begin(_begin), 116 | m_end(_end) 117 | { 118 | 119 | } 120 | 121 | ContainerView(const ContainerView & _other) : 122 | m_begin(_other.m_begin), 123 | m_end(_other.m_end) 124 | { 125 | 126 | } 127 | 128 | Iter begin() const 129 | { 130 | return Iter(m_begin); 131 | } 132 | 133 | Iter end() const 134 | { 135 | return Iter(m_end); 136 | } 137 | 138 | private: 139 | 140 | InternalIter m_begin; 141 | InternalIter m_end; 142 | }; 143 | } 144 | } 145 | 146 | #endif //PAPER_PRIVATE_CONTAINERVIEW_HPP 147 | -------------------------------------------------------------------------------- /Paper/Private/JoinAndCap.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace paper 4 | { 5 | namespace detail 6 | { 7 | void capSquare(const Vec2f & _position, const Vec2f & _direction, 8 | Vec2f & _outA, Vec2f & _outB, Vec2f & _outC, Vec2f & _outD) 9 | { 10 | Vec2f perp(-_direction.y, _direction.x); 11 | _outA = _position + perp; 12 | _outB = _position - perp; 13 | _outC = _outB + _direction; 14 | _outD = _outA + _direction; 15 | } 16 | 17 | void capOrJoinRound(stick::DynamicArray & _outPositions, const Vec2f & _position, const Vec2f & _direction, 18 | Float _theta) 19 | { 20 | Vec2f dir = _direction; 21 | dir = Vec2f(-dir.y, dir.x); 22 | 23 | //@TODO: Make this based on the stroke width 24 | stick::Size circleSubdivisionCount = 12; 25 | 26 | Float currentAngle = 0; 27 | Float radStep = _theta / (Float)(circleSubdivisionCount); 28 | _outPositions.resize(circleSubdivisionCount + 1); 29 | 30 | for (stick::Size i = 0; i <= circleSubdivisionCount; ++i) 31 | { 32 | currentAngle = radStep * i; 33 | 34 | Float cosa = cos(-currentAngle); 35 | Float sina = sin(-currentAngle); 36 | _outPositions[i] = _position + Vec2f(dir.x * cosa - dir.y * sina, 37 | dir.x * sina + dir.y * cosa); 38 | } 39 | } 40 | 41 | //returns the shortest angle between two vectors 42 | Float shortestAngle(const Vec2f & _dirA, const Vec2f & _dirB) 43 | { 44 | Float theta = std::acos(crunch::dot(_dirA, _dirB)); 45 | //make sure we have the shortest angle 46 | if (theta > crunch::Constants::halfPi()) 47 | theta = crunch::Constants::pi() - theta; 48 | 49 | return theta; 50 | } 51 | 52 | //returns the miter position 53 | Vec2f joinMiter(const Vec2f & _position, const Vec2f & _lastStart, const Vec2f & _lastDir, const Vec2f & _start, const Vec2f & _dir, Float & _outMiterLen) 54 | { 55 | Float theta = shortestAngle(_lastDir, _dir); 56 | _outMiterLen = 1.0 / std::sin(theta / 2.0); 57 | 58 | //compute the intersection 59 | Float cross = _lastDir.x * _dir.y - _lastDir.y * _dir.x; 60 | 61 | //parallel case 62 | if (cross == 0) 63 | { 64 | return _position; //due to the miter limit this return should not be used as it should switch to bevel 65 | } 66 | 67 | Vec2f dir = _start - _lastStart; 68 | Float t = (dir.x * _dir.y - dir.y * _dir.x) / cross; 69 | 70 | return _lastStart + _lastDir * t; 71 | } 72 | 73 | //computes the min max positions of a bevel 74 | void capOrJoinBevelMinMax(const Vec2f & _position, const Vec2f & _direction, Vec2f & _a, Vec2f & _b) 75 | { 76 | Vec2f perp(-_direction.y, _direction.x); 77 | _a = _position + perp; 78 | _b = _position - perp; 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /Paper/Private/JoinAndCap.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_JOINANDCAP_HPP 2 | #define PAPER_PRIVATE_JOINANDCAP_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | namespace detail 10 | { 11 | //a collection of math functions to compute joins and caps 12 | STICK_LOCAL void capSquare(const Vec2f & _position, const Vec2f & _direction, 13 | Vec2f & _outA, Vec2f & _outB, Vec2f & _outC, Vec2f & _outD); 14 | 15 | STICK_LOCAL void capOrJoinRound(stick::DynamicArray & _outPositions, 16 | const Vec2f & _position, 17 | const Vec2f & _direction, 18 | Float _theta); 19 | 20 | //returns the shortest angle between two vectors 21 | STICK_LOCAL Float shortestAngle(const Vec2f & _dirA, const Vec2f & _dirB); 22 | 23 | //returns the miter position 24 | STICK_LOCAL Vec2f joinMiter(const Vec2f & _position, 25 | const Vec2f & _lastStart, 26 | const Vec2f & _lastDir, 27 | const Vec2f & _start, 28 | const Vec2f & _dir, 29 | Float & _outMiterLen); 30 | 31 | //computes the min max positions of a bevel 32 | STICK_LOCAL void capOrJoinBevelMinMax(const Vec2f & _position, 33 | const Vec2f & _direction, 34 | Vec2f & _a, 35 | Vec2f & _b); 36 | } 37 | } 38 | 39 | #endif //PAPER_PRIVATE_JOINANDCAP_HPP 40 | -------------------------------------------------------------------------------- /Paper/Private/PathFitter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | namespace detail 10 | { 11 | using namespace stick; 12 | using namespace crunch; 13 | 14 | PathFitter::PathFitter(const Path & _p, Float64 _error, bool _bIgnoreClosed) : 15 | m_path(_p), 16 | m_error(_error), 17 | m_bIgnoreClosed(_bIgnoreClosed) 18 | { 19 | auto & segs = m_path.segmentArray(); 20 | m_positions.reserve(segs.count()); 21 | 22 | Vec2f prev, point; 23 | prev = point = Vec2f(0); 24 | // Copy over points from path and filter out adjacent duplicates. 25 | for (stick::Size i = 0; i < segs.count(); ++i) 26 | { 27 | point = segs[i]->position(); 28 | if (i < 1 || prev != point) 29 | { 30 | m_positions.append(point); 31 | prev = point; 32 | } 33 | } 34 | 35 | // printf("PREV C %lu\n", m_positions.count()); 36 | if (m_path.isClosed() && !m_bIgnoreClosed) 37 | { 38 | // printf("F %f %f L %f %f\n", m_positions[0].x, m_positions[0].y, 39 | // m_positions.last().x, m_positions.last().y); 40 | 41 | //this is kinda unefficient :( we should propably just insert that point first 42 | //rather than shifting the whole array 43 | Vec2f last = m_positions.last(); 44 | m_positions.insert(m_positions.begin(), last); 45 | m_positions.append(m_positions[1]); // The point previously at index 0 is now 1. 46 | } 47 | 48 | // for (auto & pos : m_positions) 49 | // { 50 | // printf("POS %f %f\n", pos.x, pos.y); 51 | // } 52 | 53 | // printf("POS COUNT %lu\n", m_positions.count()); 54 | } 55 | 56 | void PathFitter::fit() 57 | { 58 | if (m_positions.count() > 0) 59 | { 60 | m_newSegments.append({m_positions[0], Vec2f(0), Vec2f(0)}); 61 | 62 | stick::Size i = 0; 63 | stick::Size count = m_newSegments.count(); 64 | bool bReclose = false; 65 | 66 | if (m_positions.count() > 1) 67 | { 68 | fitCubic(0, m_positions.count() - 1, 69 | // Left Tangent 70 | m_positions[1] - m_positions[0], 71 | // Right Tangent 72 | m_positions[m_positions.count() - 2] - m_positions[m_positions.count() - 1]); 73 | 74 | if (m_path.isClosed()) 75 | { 76 | // i++; 77 | // if (count > 0) 78 | // count--; 79 | bReclose = true; 80 | m_path.set(false); 81 | 82 | // printf("F %f %f L %f %f\n", (*m_newSegments.begin()).position.x, (*m_newSegments.begin()).position.y, 83 | // m_newSegments.last().position.x, m_newSegments.last().position.y); 84 | if (!m_bIgnoreClosed) 85 | { 86 | m_newSegments.remove(m_newSegments.begin()); 87 | m_newSegments.removeLast(); 88 | } 89 | } 90 | } 91 | 92 | // m_path.removeSegments(); 93 | auto & segs = m_path.segmentArray(); 94 | segs.clear(); 95 | Document doc = m_path.document(); 96 | for (i = 0; i < m_newSegments.count(); ++i) 97 | { 98 | segs.append(stick::makeUnique(doc.allocator(), m_path, m_newSegments[i].position, m_newSegments[i].handleIn, m_newSegments[i].handleOut, segs.count())); 99 | } 100 | 101 | m_path.rebuildCurves(); 102 | 103 | if (bReclose) 104 | m_path.closePath(); 105 | } 106 | 107 | // printf("SOOOOO SIMPLEEE\n"); 108 | } 109 | 110 | void PathFitter::fitCubic(stick::Size _first, stick::Size _last, const Vec2f & _tan1, const Vec2f & _tan2) 111 | { 112 | // printf("FIRST %lu LAST %lu\n", _first, _last); 113 | // Use heuristic if region only has two points in it 114 | if (_last - _first == 1) 115 | { 116 | // printf("heuristic\n"); 117 | const Vec2f & pt1 = m_positions[_first]; 118 | const Vec2f & pt2 = m_positions[_last]; 119 | Float64 dist = crunch::distance(pt1, pt2) / 3.0; 120 | addCurve(pt1, pt1 + normalize(_tan1) * dist, 121 | pt2 + normalize(_tan2) * dist, pt2); 122 | return; 123 | } 124 | 125 | stick::DynamicArray uPrime; 126 | chordLengthParameterize(_first, _last, uPrime); 127 | 128 | // printf("NORMAL\n"); 129 | Float64 maxError = std::max(m_error, m_error * m_error); 130 | stick::Size split; 131 | bool bParametersInOrder = true; 132 | 133 | // Try 4 iterations 134 | for (stick::Int32 i = 0; i <= 4; ++i) 135 | { 136 | Bezier curve = generateBezier(_first, _last, uPrime, _tan1, _tan2); 137 | // printf("GEN BEZ %f %f, %f %f, %f %f, %f %f\n", curve.positionOne().x, curve.positionOne().y, 138 | // curve.handleOne().x, curve.handleOne().y, 139 | // curve.handleTwo().x, curve.handleTwo().y, 140 | // curve.positionTwo().x, curve.positionTwo().y); 141 | 142 | // Find max deviation of points to fitted curve 143 | auto max = findMaxError(_first, _last, curve, uPrime); 144 | 145 | if (max.value < m_error && bParametersInOrder) 146 | { 147 | // printf("ADDING CURVE\n"); 148 | addCurve(curve.positionOne(), curve.handleOne(), curve.handleTwo(), curve.positionTwo()); 149 | return; 150 | } 151 | split = max.index; 152 | 153 | // If error not too large, try reparameterization and iteration 154 | if (max.value >= maxError) 155 | { 156 | // printf("MAX ERROR %f %f %f\n", m_error, maxError, max.value); 157 | break; 158 | } 159 | 160 | bParametersInOrder = reparameterize(_first, _last, uPrime, curve); 161 | maxError = max.value; 162 | } 163 | 164 | // Fitting failed -- split at max error point and fit recursively 165 | // Vec2f v1 = m_positions[split - 1] - m_positions[split]; 166 | // Vec2f v2 = m_positions[split] - m_positions[split + 1]; 167 | // Vec2f tanCenter = crunch::normalize((v1 + v2) * 0.5); 168 | Vec2f tanCenter = m_positions[split - 1] - m_positions[split + 1]; 169 | fitCubic(_first, split, _tan1, tanCenter); 170 | fitCubic(split, _last, -tanCenter, _tan2); 171 | } 172 | 173 | void PathFitter::addCurve(const Vec2f & _pointOne, const Vec2f & _handleOne, 174 | const Vec2f & _handleTwo, const Vec2f & _pointTwo) 175 | { 176 | m_newSegments.last().handleOut = _handleOne - _pointOne; 177 | m_newSegments.append({_pointTwo, _handleTwo - _pointTwo, Vec2f(0)}); 178 | } 179 | 180 | Bezier PathFitter::generateBezier(stick::Size _first, stick::Size _last, 181 | const stick::DynamicArray & _uPrime, 182 | const Vec2f & _tan1, const Vec2f & _tan2) 183 | { 184 | static const Float64 s_epsilon = detail::PaperConstants::epsilon(); 185 | 186 | const Vec2f & pt1 = m_positions[_first]; 187 | const Vec2f & pt2 = m_positions[_last]; 188 | 189 | Float64 c[2][2] = {{0, 0}, {0, 0}}; 190 | Float64 x[2] = {0, 0}; 191 | 192 | for (stick::Size i = 0, l = _last - _first + 1; i < l; ++i) 193 | { 194 | Float64 u = _uPrime[i]; 195 | Float64 t = 1.0 - u; 196 | Float64 b = 3.0 * u * t; 197 | Float64 b0 = t * t * t; 198 | Float64 b1 = b * t; 199 | Float64 b2 = b * u; 200 | Float64 b3 = u * u * u; 201 | Vec2f a1 = normalize(_tan1) * b1; 202 | Vec2f a2 = normalize(_tan2) * b2; 203 | Vec2f tmp = m_positions[_first + i]; 204 | tmp -= pt1 * (b0 + b1); 205 | tmp -= pt2 * (b2 + b3); 206 | 207 | c[0][0] += crunch::dot(a1, a1); 208 | c[0][1] += crunch::dot(a1, a2); 209 | 210 | c[1][0] = c[0][1]; 211 | c[1][1] += crunch::dot(a2, a2); 212 | 213 | x[0] += crunch::dot(a1, tmp); 214 | x[1] += crunch::dot(a2, tmp); 215 | } 216 | 217 | Float64 detC0C1 = c[0][0] * c[1][1] - c[1][0] * c[0][1]; 218 | Float64 alpha1, alpha2; 219 | 220 | if (crunch::abs(detC0C1) > s_epsilon) 221 | { 222 | // Kramer's rule 223 | Float64 detC0X = c[0][0] * x[1] - c[1][0] * x[0]; 224 | Float64 detXC1 = x[0] * c[1][1] - x[1] * c[0][1]; 225 | // Derive alpha values 226 | alpha1 = detXC1 / detC0C1; 227 | alpha2 = detC0X / detC0C1; 228 | } 229 | else 230 | { 231 | // Matrix is under-determined, try assuming alpha1 == alpha2 232 | Float64 c0 = c[0][0] + c[0][1]; 233 | Float64 c1 = c[1][0] + c[1][1]; 234 | 235 | if (crunch::abs(c0) > s_epsilon) 236 | { 237 | alpha1 = alpha2 = x[0] / c0; 238 | } 239 | else if (crunch::abs(c1) > s_epsilon) 240 | { 241 | alpha1 = alpha2 = x[1] / c1; 242 | } 243 | else 244 | { 245 | // Handle below 246 | alpha1 = alpha2 = 0; 247 | } 248 | } 249 | 250 | // If alpha negative, use the Wu/Barsky heuristic (see text) 251 | // (if alpha is 0, you get coincident control points that lead to 252 | // divide by zero in any subsequent NewtonRaphsonRootFind() call. 253 | Float64 segLength = crunch::distance(pt1, pt2); 254 | Float64 epsilon = s_epsilon * segLength; 255 | Vec2f handleOne(0); 256 | Vec2f handleTwo(0); 257 | if (alpha1 < epsilon || alpha2 < epsilon) 258 | { 259 | // fall back on standard (probably inaccurate) formula, 260 | // and subdivide further if needed. 261 | alpha1 = alpha2 = segLength / 3.0; 262 | } 263 | else 264 | { 265 | // Check if the found control points are in the right order when 266 | // projected onto the line through pt1 and pt2. 267 | Vec2f line = pt2 - pt1; 268 | 269 | handleOne = normalize(_tan1) * alpha1; 270 | handleTwo = normalize(_tan2) * alpha2; 271 | 272 | if (crunch::dot(handleOne, line) - crunch::dot(handleTwo, line) > segLength * segLength) 273 | { 274 | // Fall back to the Wu/Barsky heuristic above. 275 | alpha1 = alpha2 = segLength / 3.0; 276 | handleOne = normalize(_tan1) * alpha1; 277 | handleTwo = normalize(_tan2) * alpha2; 278 | } 279 | } 280 | 281 | // First and last control points of the Bezier curve are 282 | // positioned exactly at the first and last data points 283 | // Control points 1 and 2 are positioned an alpha distance out 284 | // on the tangent vectors, left and right, respectively 285 | // printf("PT1, %f %f, PT2 %f %f\n", pt1.x, pt1.y, handleOne.x, handleOne.y); 286 | return Bezier(pt1, pt1 + handleOne, 287 | pt2 + handleTwo, pt2); 288 | } 289 | 290 | Vec2f PathFitter::evaluate(stick::Int32 _degree, const Bezier & _curve, Float64 _t) 291 | { 292 | Vec2f tmp[4] = {_curve.positionOne(), _curve.handleOne(), _curve.handleTwo(), _curve.positionTwo()}; 293 | // printf("BEZ %f %f, %f %f, %f %f, %f %f\n", _curve.positionOne().x, _curve.positionOne().y, 294 | // _curve.handleOne().x, _curve.handleOne().y, 295 | // _curve.handleTwo().x, _curve.handleTwo().y, 296 | // _curve.positionTwo().x, _curve.positionTwo().y); 297 | for (stick::Int32 i = 1; i <= _degree; ++i) 298 | { 299 | for (stick::Int32 j = 0; j <= _degree - i; ++j) 300 | { 301 | tmp[j] = tmp[j] * (1 - _t) + tmp[j + 1] * _t; 302 | } 303 | } 304 | 305 | return tmp[0]; 306 | } 307 | 308 | bool PathFitter::reparameterize(stick::Size _first, stick::Size _last, 309 | stick::DynamicArray & _u, const Bezier & _curve) 310 | { 311 | // printf("REPARA\n"); 312 | for (stick::Size i = _first; i <= _last; ++i) 313 | { 314 | _u[i - _first] = findRoot(_curve, m_positions[i], _u[i - _first]); 315 | } 316 | 317 | // Detect if the new parameterization has reordered the points. 318 | // In that case, we would fit the points of the path in the wrong order. 319 | for (stick::Size i = 1; i < _u.count(); ++i) 320 | { 321 | if (_u[i] <= _u[i - 1]) 322 | return false; 323 | } 324 | 325 | return true; 326 | } 327 | 328 | Float64 PathFitter::findRoot(const Bezier & _curve, const Vec2f & _point, Float64 _u) 329 | { 330 | static Float64 s_tolerance = detail::PaperConstants::tolerance(); 331 | 332 | Bezier curve1; 333 | Bezier curve2; 334 | 335 | // control vertices for Q' 336 | curve1.setPositionOne((_curve.handleOne() - _curve.positionOne()) * 3.0); 337 | curve1.setHandleOne((_curve.handleTwo() - _curve.handleOne()) * 3.0); 338 | curve1.setHandleTwo((_curve.positionTwo() - _curve.handleTwo()) * 3.0); 339 | 340 | // control vertices for Q'' 341 | curve2.setPositionOne((curve1.handleOne() - curve1.positionOne()) * 2.0); 342 | curve2.setHandleOne((curve1.handleTwo() - curve1.handleOne()) * 2.0); 343 | 344 | // Compute Q(u), Q'(u) and Q''(u) 345 | Vec2f pt = evaluate(3, _curve, _u); 346 | Vec2f pt1 = evaluate(2, curve1, _u); 347 | Vec2f pt2 = evaluate(1, curve2, _u); 348 | Vec2f diff = pt - _point; 349 | Float64 df = crunch::dot(pt1, pt1) + crunch::dot(diff, pt2); 350 | 351 | // u = u - f(u) / f'(u) 352 | return crunch::isClose(df, (Float64)0.0) ? _u : _u - crunch::dot(diff, pt1) / df; 353 | } 354 | 355 | void PathFitter::chordLengthParameterize(stick::Size _first, stick::Size _last, 356 | stick::DynamicArray & _outResult) 357 | { 358 | stick::Size size = _last - _first; 359 | _outResult.resize(size + 1); 360 | _outResult[0] = 0; 361 | for (stick::Size i = _first + 1; i <= _last; ++i) 362 | { 363 | _outResult[i - _first] = _outResult[i - _first - 1] 364 | + crunch::distance(m_positions[i], m_positions[i - 1]); 365 | } 366 | for (stick::Size i = 1; i <= size; i++) 367 | _outResult[i] /= _outResult[size]; 368 | } 369 | 370 | PathFitter::MaxError PathFitter::findMaxError(stick::Size _first, stick::Size _last, 371 | const Bezier & _curve, const stick::DynamicArray & _u) 372 | { 373 | stick::Size index = crunch::floor((_last - _first + 1) / 2.0); 374 | Float64 maxDist = 0; 375 | for (stick::Size i = _first + 1; i < _last; ++i) 376 | { 377 | Vec2f p = evaluate(3, _curve, _u[i - _first]); 378 | // printf("P %f %f\n", p.x, p.y); 379 | Vec2f v = p - m_positions[i]; 380 | Float64 dist = v.x * v.x + v.y * v.y; // squared 381 | // printf("D %f\n", dist); 382 | if (dist >= maxDist) 383 | { 384 | maxDist = dist; 385 | index = i; 386 | } 387 | } 388 | return {maxDist, index}; 389 | } 390 | } 391 | } 392 | -------------------------------------------------------------------------------- /Paper/Private/PathFitter.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_PATHFITTER_HPP 2 | #define PAPER_PRIVATE_PATHFITTER_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class Path; 9 | 10 | namespace detail 11 | { 12 | // From paper.js source 13 | // An Algorithm for Automatically Fitting Digitized Curves 14 | // by Philip J. Schneider 15 | // from "Graphics Gems", Academic Press, 1990 16 | // Modifications and optimisations of original algorithm by Juerg Lehni. 17 | 18 | class PathFitter 19 | { 20 | public: 21 | 22 | struct MaxError 23 | { 24 | stick::Float64 value; 25 | stick::Size index; 26 | }; 27 | 28 | struct SegmentDesc 29 | { 30 | Vec2f position; 31 | Vec2f handleIn; 32 | Vec2f handleOut; 33 | }; 34 | 35 | using SegmentDescArray = stick::DynamicArray; 36 | using PositionArray = stick::DynamicArray; 37 | 38 | PathFitter(const Path & _p, stick::Float64 _error, bool _bIgnoreClosed); 39 | 40 | void fit(); 41 | 42 | void fitCubic(stick::Size _first, stick::Size _last, const Vec2f & _tan1, const Vec2f & _tan2); 43 | 44 | void addCurve(const Vec2f & _pointOne, const Vec2f & _handleOne, 45 | const Vec2f & _handleTwo, const Vec2f & _pointTwo); 46 | 47 | Bezier generateBezier(stick::Size _first, stick::Size _last, 48 | const stick::DynamicArray & _uPrime, 49 | const Vec2f & _tan1, const Vec2f & _tan2); 50 | 51 | // Evaluate a Bezier curve at a particular parameter value 52 | Vec2f evaluate(stick::Int32 _degree, const Bezier & _curve, stick::Float64 _t); 53 | 54 | // Given set of points and their parameterization, try to find 55 | // a better parameterization. 56 | bool reparameterize(stick::Size _first, stick::Size _last, 57 | stick::DynamicArray & _u, const Bezier & _curve); 58 | 59 | // Use Newton-Raphson iteration to find better root. 60 | stick::Float64 findRoot(const Bezier & _curve, const Vec2f & _point, stick::Float64 _u); 61 | 62 | // Assign parameter values to digitized points 63 | // using relative distances between points. 64 | void chordLengthParameterize(stick::Size _first, stick::Size _last, 65 | stick::DynamicArray & _outResult); 66 | 67 | MaxError findMaxError(stick::Size _first, stick::Size _last, 68 | const Bezier & _curve, const stick::DynamicArray & _u); 69 | 70 | private: 71 | 72 | Path m_path; 73 | Float m_error; 74 | bool m_bIgnoreClosed; 75 | SegmentDescArray m_newSegments; 76 | PositionArray m_positions; 77 | }; 78 | } 79 | } 80 | 81 | #endif //PAPER_PRIVATE_PATHFITTER_HPP 82 | -------------------------------------------------------------------------------- /Paper/Private/PathFlattener.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace paper 9 | { 10 | namespace detail 11 | { 12 | bool PathFlattener::isFlatEnough(const Bezier & _curve, Float _tolerance) 13 | { 14 | if (_curve.isLinear()) 15 | return true; 16 | 17 | // Comment from paper.js source in Curve.js: 18 | // Thanks to Kaspar Fischer and Roger Willcocks for the following: 19 | // http://hcklbrrfnn.files.wordpress.com/2012/08/bez.pdf 20 | Vec2f u = _curve.handleOne() * 3.0 - _curve.positionOne() * 2.0 - _curve.positionTwo(); 21 | Vec2f v = _curve.handleTwo() * 3.0 - _curve.positionTwo() * 2.0 - _curve.positionOne(); 22 | auto val = std::max(u.x * u.x, v.x * v.x) + std::max(u.y * u.y, v.y * v.y); 23 | //STICK_ASSERT(!std::isnan(val)); 24 | return val < 10.0 * _tolerance * _tolerance; 25 | } 26 | 27 | void PathFlattener::flatten(const Path & _path, PositionArray & _outPositions, JoinArray * _outJoins, 28 | Float _angleTolerance, Float _minDistance, stick::Size _maxRecursionDepth) 29 | { 30 | auto & curves = _path.get(); 31 | auto it = curves.begin(); 32 | for (; it != curves.end(); ++it) 33 | { 34 | flattenCurve((*it)->bezier(), (*it)->bezier(), _outPositions, _outJoins, _angleTolerance, _minDistance, 0, _maxRecursionDepth, _path.isClosed(), it == curves.end() - 1); 35 | } 36 | } 37 | 38 | void PathFlattener::flattenCurve(const Bezier & _curve, const Bezier & _initialCurve, PositionArray & _outPositions, JoinArray * _outJoins, 39 | Float _angleTolerance, Float _minDistance, stick::Size _recursionDepth, stick::Size _maxRecursionDepth, bool _bIsClosed, bool _bLastCurve) 40 | { 41 | if (_recursionDepth < _maxRecursionDepth && !isFlatEnough(_curve, _angleTolerance)) 42 | { 43 | Bezier::Pair curves = _curve.subdivide(0.5); 44 | flattenCurve(curves.first, _initialCurve, _outPositions, _outJoins, _angleTolerance, _minDistance, _recursionDepth + 1, _maxRecursionDepth, _bIsClosed, _bLastCurve); 45 | flattenCurve(curves.second, _initialCurve, _outPositions, _outJoins, _angleTolerance, _minDistance, _recursionDepth + 1, _maxRecursionDepth, _bIsClosed, _bLastCurve); 46 | } 47 | else 48 | { 49 | Float minDistSquared = _minDistance * _minDistance; 50 | if (_outPositions.count()) 51 | { 52 | if (crunch::distanceSquared(_curve.positionTwo(), _outPositions.last()) >= minDistSquared) 53 | { 54 | _outPositions.append(_curve.positionTwo()); 55 | if (_outJoins) 56 | { 57 | if (_bIsClosed) 58 | _outJoins->append(_curve.positionTwo() == _initialCurve.positionTwo()); 59 | else 60 | _outJoins->append(_curve.positionTwo() == _initialCurve.positionTwo() && !_bLastCurve); 61 | } 62 | } 63 | } 64 | else 65 | { 66 | //for the first curve we also add its first segment 67 | _outPositions.append(_curve.positionOne()); 68 | if (_outJoins) 69 | _outJoins->append(false); 70 | _outPositions.append(_curve.positionTwo()); 71 | if (_outJoins) 72 | { 73 | _outJoins->append(_curve.positionTwo() == _initialCurve.positionTwo() && !_bLastCurve); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Paper/Private/PathFlattener.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_PATHFLATTENER_HPP 2 | #define PAPER_PRIVATE_PATHFLATTENER_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | namespace paper 9 | { 10 | class Path; 11 | 12 | namespace detail 13 | { 14 | struct STICK_LOCAL PathFlattener 15 | { 16 | typedef stick::DynamicArray PositionArray; 17 | typedef stick::DynamicArray JoinArray; 18 | 19 | static bool isFlatEnough(const Bezier & _curve, Float _tolerance); 20 | 21 | static void flatten(const Path & _path, PositionArray & _outPositions, JoinArray * _outJoins, 22 | Float _angleTolerance, Float _minDistance, stick::Size _maxRecursionDepth); 23 | 24 | static void flattenCurve(const Bezier & _curve, const Bezier & _initialCurve, PositionArray & _outPositions, JoinArray * _outJoins, 25 | Float _angleTolerance, Float _minDistance, 26 | stick::Size _recursionDepth, stick::Size _maxRecursionDepth, bool _bIsClosed, bool _bLastCurve); 27 | }; 28 | } 29 | } 30 | 31 | #endif //PAPER_PRIVATE_PATHFLATTENER_HPP 32 | -------------------------------------------------------------------------------- /Paper/Private/Shape.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | namespace detail 10 | { 11 | Shape::Shape() : 12 | m_type(ShapeType::None) 13 | { 14 | 15 | } 16 | 17 | Shape::Shape(const Path & _path) : 18 | m_type(ShapeType::None) 19 | { 20 | const auto & curves = _path.curveArray(); 21 | const auto & segments = _path.segmentArray(); 22 | if (curves.count() == 4 && 23 | curves[0]->isArc() && 24 | curves[1]->isArc() && 25 | curves[2]->isArc() && 26 | curves[3]->isArc()) 27 | { 28 | if (crunch::isClose(crunch::length(segments[0]->position() - segments[2]->position()) - 29 | crunch::length(segments[1]->position() - segments[3]->position()), (Float)0, PaperConstants::epsilon())) 30 | { 31 | m_type = ShapeType::Circle; 32 | m_data.circle.position = _path.localBounds().center(); 33 | m_data.circle.radius = crunch::distance(segments[0]->position(), segments[2]->position()) * 0.5; 34 | } 35 | else 36 | { 37 | if (crunch::isClose(crunch::distance(segments[0]->position(), segments[2]->position()), (Float)0, PaperConstants::epsilon())) 38 | { 39 | m_type = ShapeType::Circle; 40 | m_data.circle.position = _path.localBounds().center(); 41 | m_data.circle.radius = crunch::distance(segments[0]->position(), segments[2]->position()) * 0.5; 42 | } 43 | else 44 | { 45 | m_type = ShapeType::Ellipse; 46 | m_data.ellipse.position = _path.localBounds().center(); 47 | m_data.ellipse.size = Vec2f(crunch::distance(segments[0]->position(), segments[2]->position()), 48 | crunch::distance(segments[1]->position(), segments[3]->position())); 49 | } 50 | } 51 | } 52 | else if (_path.isPolygon() && 53 | curves.count() == 4 && 54 | curves[0]->isCollinear(*curves[2]) && 55 | curves[1]->isCollinear(*curves[3]) && 56 | curves[1]->isOrthogonal(*curves[0])) 57 | { 58 | m_type = ShapeType::Rectangle; 59 | m_data.rectangle.position = _path.localBounds().center(); 60 | 61 | Float w = crunch::distance(segments[0]->position(), 62 | segments[1]->position()); 63 | Float h = crunch::distance(segments[1]->position(), 64 | segments[2]->position()); 65 | if (!crunch::isClose(segments[0]->position().y, segments[1]->position().y)) 66 | { 67 | std::swap(w, h); 68 | } 69 | m_data.rectangle.size = Vec2f(w, h); 70 | m_data.rectangle.cornerRadius = Vec2f(0); 71 | } 72 | else if (curves.count() == 8 && 73 | curves[1]->isArc() && 74 | curves[3]->isArc() && 75 | curves[5]->isArc() && 76 | curves[7]->isArc() && 77 | curves[0]->isCollinear(*curves[4]) && 78 | curves[2]->isCollinear(*curves[6])) 79 | { 80 | 81 | //rounded rect 82 | m_type = ShapeType::Rectangle; 83 | 84 | m_data.rectangle.position = _path.localBounds().center(); 85 | m_data.rectangle.size = Vec2f(crunch::distance(segments[7]->position(), 86 | segments[2]->position()), 87 | crunch::distance(segments[0]->position(), 88 | segments[5]->position())); 89 | m_data.rectangle.cornerRadius = (m_data.rectangle.size - Vec2f(crunch::distance(segments[0]->position(), 90 | segments[1]->position()), 91 | crunch::distance(segments[2]->position(), 92 | segments[3]->position()))) * 0.5; 93 | 94 | } 95 | 96 | } 97 | 98 | ShapeType Shape::shapeType() const 99 | { 100 | return m_type; 101 | } 102 | 103 | const Shape::Circle & Shape::circle() const 104 | { 105 | return m_data.circle; 106 | } 107 | 108 | const Shape::Ellipse & Shape::ellipse() const 109 | { 110 | return m_data.ellipse; 111 | } 112 | 113 | const Shape::Rectangle & Shape::rectangle() const 114 | { 115 | return m_data.rectangle; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /Paper/Private/Shape.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_SHAPE_HPP 2 | #define PAPER_PRIVATE_SHAPE_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class Path; 9 | 10 | namespace detail 11 | { 12 | STICK_LOCAL_ENUM_CLASS(ShapeType) 13 | { 14 | None, 15 | Rectangle, //also rounded rect 16 | Circle, 17 | Ellipse 18 | }; 19 | 20 | class STICK_LOCAL Shape 21 | { 22 | public: 23 | 24 | struct Circle 25 | { 26 | Vec2f position; 27 | Float radius; 28 | }; 29 | 30 | struct Ellipse 31 | { 32 | Vec2f position; 33 | Vec2f size; 34 | }; 35 | 36 | struct Rectangle 37 | { 38 | Vec2f position; 39 | Vec2f size; 40 | Vec2f cornerRadius; 41 | }; 42 | 43 | Shape(); 44 | 45 | Shape(const Path & _path); 46 | 47 | ShapeType shapeType() const; 48 | 49 | const Circle & circle() const; 50 | 51 | const Ellipse & ellipse() const; 52 | 53 | const Rectangle & rectangle() const; 54 | 55 | 56 | private: 57 | 58 | ShapeType m_type; 59 | 60 | union Data 61 | { 62 | Data() 63 | { 64 | 65 | } 66 | 67 | Circle circle; 68 | Ellipse ellipse; 69 | Rectangle rectangle; 70 | }; 71 | 72 | Data m_data; 73 | }; 74 | } 75 | } 76 | 77 | #endif //PAPER_PRIVATE_SHAPE_HPP 78 | -------------------------------------------------------------------------------- /Paper/Private/StrokeTriangulator.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_PRIVATE_STROKETRIANGULATOR_HPP 2 | #define PAPER_PRIVATE_STROKETRIANGULATOR_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Path; 10 | 11 | namespace detail 12 | { 13 | class STICK_LOCAL StrokeTriangulator 14 | { 15 | public: 16 | 17 | typedef stick::DynamicArray PositionArray; 18 | typedef stick::DynamicArray JoinArray; 19 | 20 | 21 | StrokeTriangulator(const Mat3f & _strokeMat, const Mat3f & _invStrokeMat, 22 | StrokeJoin _join, StrokeCap _cap, 23 | Float _miterLimit, bool _bIsClosed, const DashArray & _dashArray, Float _dashOffset); 24 | 25 | 26 | static Float shortestAngle(const Vec2f & _dirA, const Vec2f & _dirB); 27 | 28 | void makeCapOrJoinRound(const Vec2f & _point, const Vec2f & _dir, 29 | PositionArray & _outVertices, Float _theta); 30 | 31 | void makeCapRound(const Vec2f & _point, const Vec2f & _dir, 32 | PositionArray & _outVertices, bool _bStart); 33 | 34 | void makeCapSquare(const Vec2f & _point, const Vec2f & _dir, 35 | PositionArray & _outVertices, bool _bStart); 36 | 37 | void makeJoinMiter(const Vec2f & _point, const Vec2f & _lastDir, const Vec2f & _dir, 38 | const Vec2f & _leftEdgePoint, const Vec2f & _rightEdgePoint, 39 | const Vec2f & _lastLeftEdgePoint, const Vec2f & _lastRightEdgePoint, 40 | PositionArray & _outVertices); 41 | 42 | void makeJoinBevel(const Vec2f & _point, const Vec2f & _lastDir, const Vec2f & _dir, 43 | const Vec2f & _leftEdgePoint, const Vec2f & _rightEdgePoint, 44 | const Vec2f & _lastLeftEdgePoint, const Vec2f & _lastRightEdgePoint, 45 | PositionArray & _outVertices); 46 | 47 | void makeJoinRound(const Vec2f & _point, const Vec2f & _lastDir, const Vec2f & _dir, 48 | PositionArray & _outVertices); 49 | 50 | void makeJoin(const Vec2f & _point, const Vec2f & _lastDir, const Vec2f & _dir, 51 | const Vec2f & _leftEdgePoint, const Vec2f & _rightEdgePoint, 52 | const Vec2f & _lastLeftEdgePoint, const Vec2f & _lastRightEdgePoint, 53 | PositionArray & _outVertices); 54 | 55 | //this function fills _outVertices with all the vertices necessary to render the stroke as a triangle strip 56 | void triangulateStroke(const PositionArray & _positions, const JoinArray & _joins, PositionArray & _outVertices, bool _bIsClockwise); 57 | 58 | private: 59 | 60 | void triangulateStrokeImpl(const PositionArray & _positions, const JoinArray & _joins, PositionArray & _outVertices, bool _bDashing, bool _bIsClockwise); 61 | 62 | Mat3f m_strokeMat; 63 | Mat3f m_invStrokeMat; 64 | StrokeJoin m_join; 65 | StrokeCap m_cap; 66 | Float m_miterLimit; 67 | bool m_bIsClosed; 68 | DashArray m_dashArray; 69 | Float m_dashOffset; 70 | bool m_bTriangleStrip; 71 | }; 72 | } 73 | } 74 | 75 | #endif //PAPER_PRIVATE_STROKETRIANGULATOR_HPP 76 | -------------------------------------------------------------------------------- /Paper/RenderInterface.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | namespace paper 9 | { 10 | RenderInterface::RenderInterface() 11 | { 12 | 13 | } 14 | 15 | RenderInterface::~RenderInterface() 16 | { 17 | 18 | } 19 | 20 | stick::Error RenderInterface::draw() 21 | { 22 | STICK_ASSERT(m_document.isValid()); 23 | stick::Error ret = prepareDrawing(); 24 | if (ret) return ret; 25 | ret = drawChildren(m_document, nullptr); 26 | if (ret) return ret; 27 | ret = finishDrawing(); 28 | return ret; 29 | } 30 | 31 | stick::Error RenderInterface::drawChildren(Item _item, const Mat3f * _transform) 32 | { 33 | const auto & children = _item.children(); 34 | stick::Error err; 35 | for (const auto & c : children) 36 | { 37 | err = drawItem(c, _transform); 38 | if (err) return err; 39 | } 40 | return err; 41 | } 42 | 43 | stick::Error RenderInterface::drawItem(Item _item, const Mat3f * _transform) 44 | { 45 | auto et = _item.get(); 46 | stick::Error ret; 47 | 48 | Mat3f tmp; 49 | if (_transform) 50 | tmp = _item.absoluteTransform() * *_transform; 51 | 52 | if (et == EntityType::Group) 53 | { 54 | Group grp = brick::reinterpretEntity(_item); 55 | if (!grp.isVisible()) 56 | return ret; 57 | 58 | if (grp.isClipped()) 59 | { 60 | const auto & c2 = grp.children(); 61 | STICK_ASSERT(c2.first().get() == EntityType::Path); 62 | Path mask = brick::reinterpretEntity(c2.first()); 63 | Mat3f tmp2; 64 | if (_transform) 65 | tmp2 = mask.absoluteTransform() * tmp; 66 | ret = beginClipping(mask, _transform ? tmp2 : mask.absoluteTransform()); 67 | if (ret) return ret; 68 | auto it = c2.begin() + 1; 69 | for (; it != c2.end(); ++it) 70 | { 71 | ret = drawItem(Item(*it), _transform); 72 | if (ret) return ret; 73 | } 74 | ret = endClipping(); 75 | } 76 | else 77 | drawChildren(_item, _transform); 78 | } 79 | else if (et == EntityType::Path) 80 | { 81 | Path p = brick::reinterpretEntity(_item); 82 | if (p.isVisible() && p.segmentArray().count() > 1) 83 | { 84 | ret = drawPath(p, _transform ? tmp : p.absoluteTransform()); 85 | } 86 | } 87 | else if (et == EntityType::PlacedSymbol) 88 | { 89 | PlacedSymbol ps = brick::reinterpretEntity(_item); 90 | drawItem(ps.symbol().item(), _transform ? &tmp : &ps.absoluteTransform()); 91 | } 92 | return ret; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Paper/RenderInterface.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_RENDERINTERFACE_HPP 2 | #define PAPER_RENDERINTERFACE_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Document; 10 | class Path; 11 | class Item; 12 | 13 | class STICK_API RenderInterface 14 | { 15 | public: 16 | 17 | RenderInterface(); 18 | 19 | virtual ~RenderInterface(); 20 | 21 | 22 | virtual stick::Error init(Document _doc) = 0; 23 | 24 | stick::Error draw(); 25 | 26 | virtual void setViewport(Float _x, Float _y, 27 | Float _widthInPixels, Float _heightInPixels) = 0; 28 | 29 | virtual void setProjection(const Mat4f & _projection) = 0; 30 | 31 | virtual void reserveItems(stick::Size _count) = 0; 32 | 33 | 34 | protected: 35 | 36 | //these have to be implemented 37 | virtual stick::Error drawPath(Path _path, const Mat3f & _transform) = 0; 38 | virtual stick::Error beginClipping(Path _clippingPath, const Mat3f & _transform) = 0; 39 | virtual stick::Error endClipping() = 0; 40 | 41 | //these can be implemented 42 | virtual stick::Error prepareDrawing() { return stick::Error(); } 43 | virtual stick::Error finishDrawing() { return stick::Error(); } 44 | 45 | stick::Error drawChildren(Item _item, const Mat3f * _transform); 46 | stick::Error drawItem(Item _item, const Mat3f * _transform); 47 | 48 | Document m_document; 49 | }; 50 | } 51 | 52 | #endif //PAPER_RENDERINTERFACE_HPP 53 | -------------------------------------------------------------------------------- /Paper/SVG/SVGExport.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_SVG_SVGEXPORT_HPP 2 | #define PAPER_SVG_SVGEXPORT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class Document; 10 | class Item; 11 | class Group; 12 | class Path; 13 | class Curve; 14 | 15 | namespace svg 16 | { 17 | using namespace scrub; 18 | 19 | class STICK_LOCAL SVGExport 20 | { 21 | public: 22 | 23 | SVGExport(); 24 | 25 | stick::TextResult exportDocument(const Document & _document); 26 | 27 | void addToDefsNode(Shrub & _node); 28 | 29 | void exportItem(const Item & _item, Shrub & _parentTreeNode, bool _bIsClipMask); 30 | 31 | void setTransform(const Item & _item, Shrub & _node); 32 | 33 | void exportGroup(const Group & _group, Shrub & _parentTreeNode); 34 | 35 | void exportCurveData(const Path & _path, Shrub & _parentTreeNode, Shrub *& _pn); 36 | 37 | void exportPath(const Path & _path, Shrub & _parentTreeNode, bool _bIsClipPath, bool _bMatchShape); 38 | 39 | void applyStyle(const Item & _item, Shrub & _node); 40 | 41 | 42 | 43 | Shrub m_tree; 44 | stick::Size m_clipMaskID; 45 | }; 46 | } 47 | } 48 | 49 | #endif //PAPER_SVG_SVGEXPORT_HPP 50 | -------------------------------------------------------------------------------- /Paper/SVG/SVGImport.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_SVG_SVGIMPORT_HPP 2 | #define PAPER_SVG_SVGIMPORT_HPP 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace paper 15 | { 16 | class Document; 17 | class Path; 18 | 19 | namespace svg 20 | { 21 | using namespace scrub; 22 | 23 | enum class SVGUnits 24 | { 25 | EM, 26 | EX, 27 | PX, 28 | PT, 29 | PC, 30 | CM, 31 | MM, 32 | IN, 33 | Percent, 34 | User 35 | }; 36 | 37 | struct STICK_LOCAL SVGAttributes 38 | { 39 | ColorRGBA fillColor; 40 | WindingRule windingRule; 41 | ColorRGBA strokeColor; 42 | Float strokeWidth; 43 | StrokeCap strokeCap; 44 | StrokeJoin strokeJoin; 45 | bool bScalingStroke; 46 | Float miterLimit; 47 | stick::DynamicArray dashArray; 48 | Float dashOffset; 49 | // since we dont support text yet, we only care 50 | // about font size for em/ex calculations 51 | Float fontSize; 52 | }; 53 | 54 | struct STICK_LOCAL SVGCoordinate 55 | { 56 | SVGUnits units; 57 | Float value; 58 | }; 59 | 60 | struct STICK_LOCAL SVGView 61 | { 62 | Rect rectangle; 63 | Mat3f viewBoxTransform; 64 | }; 65 | 66 | class STICK_LOCAL SVGImport 67 | { 68 | public: 69 | 70 | SVGImport(); 71 | 72 | SVGImport(Document & _doc); 73 | 74 | SVGImportResult parse(const stick::String & _svg, stick::Size _dpi = 72); 75 | 76 | Item recursivelyImportNode(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 77 | 78 | Group importGroup(const Shrub & _node, const Shrub & _rootNode, bool _bSVGNode, stick::Error & _error); 79 | 80 | Path importClipPath(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 81 | 82 | Path importPath(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 83 | 84 | Path importPolyline(const Shrub & _node, const Shrub & _rootNode, bool _bIsPolygon, stick::Error & _error); 85 | 86 | Path importCircle(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 87 | 88 | Path importEllipse(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 89 | 90 | Path importRectangle(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 91 | 92 | Path importLine(const Shrub & _node, const Shrub & _rootNode, stick::Error & _error); 93 | 94 | static void parsePathData(Document & _doc, Path _path, const stick::String & _data); 95 | 96 | // // Not supported yet 97 | // void importText(); 98 | // // Not supported yet 99 | // void importSymbol(); 100 | // // Not supported yet 101 | // void importUse(); 102 | 103 | private: 104 | 105 | Float toPixels(Float _value, SVGUnits _units, Float _start = 0.0, Float _length = 1.0); 106 | 107 | SVGCoordinate parseCoordinate(const char * _str); 108 | 109 | Float coordinatePixels(const char * _str, Float _start = 0.0, Float _length = 1.0); 110 | 111 | void parseAttribute(const stick::String & _name, const stick::String & _value, 112 | SVGAttributes & _attr, Item & _item); 113 | 114 | void parseStyle(const stick::String & _style, SVGAttributes & _attr, Item & _item); 115 | 116 | void pushAttributes(const Shrub & _node, const Shrub & _rootNode, Item & _item); 117 | 118 | void popAttributes(); 119 | 120 | 121 | Document * m_document; 122 | stick::Size m_dpi; 123 | stick::DynamicArray m_attributeStack; 124 | stick::DynamicArray m_viewStack; 125 | // for items that are only temporary in the dom and should 126 | // be removed at the end of the import (i.e. clipping masks, defs nodes etc.) 127 | stick::DynamicArray m_tmpItems; 128 | stick::HashMap m_namedItems; 129 | }; 130 | } 131 | } 132 | 133 | #endif //PAPER_SVG_SVGIMPORT_HPP 134 | -------------------------------------------------------------------------------- /Paper/SVG/SVGImportResult.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | namespace paper 4 | { 5 | namespace svg 6 | { 7 | using namespace stick; 8 | 9 | SVGImportResult::SVGImportResult() 10 | { 11 | 12 | } 13 | 14 | SVGImportResult::SVGImportResult(const Error & _err) : 15 | m_group(Group()), 16 | m_width(0), 17 | m_height(0), 18 | m_error(_err) 19 | { 20 | 21 | } 22 | 23 | SVGImportResult::SVGImportResult(Group _grp, Float _width, Float _height, const Error & _err) : 24 | m_group(_grp), 25 | m_width(_width), 26 | m_height(_height), 27 | m_error(_err) 28 | { 29 | 30 | } 31 | 32 | SVGImportResult::operator bool() const 33 | { 34 | return !static_cast(m_error); 35 | } 36 | 37 | Group SVGImportResult::group() const 38 | { 39 | return m_group; 40 | } 41 | 42 | Float SVGImportResult::width() const 43 | { 44 | return m_width; 45 | } 46 | 47 | Float SVGImportResult::height() const 48 | { 49 | return m_height; 50 | } 51 | 52 | const Error & SVGImportResult::error() const 53 | { 54 | return m_error; 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /Paper/SVG/SVGImportResult.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_SVG_SVGIMPORTRESULT_HPP 2 | #define PAPER_SVG_SVGIMPORTRESULT_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | namespace svg 10 | { 11 | class STICK_API SVGImportResult 12 | { 13 | public: 14 | 15 | SVGImportResult(); 16 | 17 | SVGImportResult(const stick::Error & _err); 18 | 19 | SVGImportResult(Group _grp, Float _width, Float _height, const stick::Error & _err = stick::Error()); 20 | 21 | explicit operator bool() const; 22 | 23 | Group group() const; 24 | 25 | Float width() const; 26 | 27 | Float height() const; 28 | 29 | const stick::Error & error() const; 30 | 31 | private: 32 | 33 | Group m_group; 34 | Float m_width, m_height; 35 | stick::Error m_error; 36 | }; 37 | } 38 | } 39 | 40 | #endif //PAPER_SVG_SVGIMPORTRESULT_HPP 41 | -------------------------------------------------------------------------------- /Paper/Segment.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace paper 6 | { 7 | Segment::Segment(const Path & _path, 8 | const Vec2f & _pos, 9 | const Vec2f & _handleIn, 10 | const Vec2f & _handleOut, 11 | stick::Size _idx) : 12 | m_path(_path), 13 | m_position(_pos), 14 | m_handleIn(_handleIn), 15 | m_handleOut(_handleOut), 16 | m_index(_idx) 17 | { 18 | } 19 | 20 | void Segment::setPosition(const Vec2f & _pos) 21 | { 22 | m_position = _pos; 23 | m_path.segmentChanged(*this); 24 | } 25 | 26 | void Segment::setHandleIn(const Vec2f & _pos) 27 | { 28 | m_handleIn = _pos; 29 | m_path.segmentChanged(*this); 30 | } 31 | 32 | void Segment::setHandleOut(const Vec2f & _pos) 33 | { 34 | m_handleOut = _pos; 35 | m_path.segmentChanged(*this); 36 | } 37 | 38 | const Vec2f & Segment::position() const 39 | { 40 | return m_position; 41 | } 42 | 43 | const Vec2f & Segment::handleIn() const 44 | { 45 | return m_handleIn; 46 | } 47 | 48 | const Vec2f & Segment::handleOut() const 49 | { 50 | return m_handleOut; 51 | } 52 | 53 | Vec2f Segment::handleInAbsolute() const 54 | { 55 | return m_position + m_handleIn; 56 | } 57 | 58 | Vec2f Segment::handleOutAbsolute() const 59 | { 60 | return m_position + m_handleOut; 61 | } 62 | 63 | const Curve * Segment::curveIn() const 64 | { 65 | return const_cast(this)->curveIn(); 66 | } 67 | 68 | Curve * Segment::curveIn() 69 | { 70 | if (m_index == 0) 71 | { 72 | if (!m_path.isClosed()) 73 | return nullptr; 74 | else 75 | return m_path.curveArray().last().get(); 76 | } 77 | else 78 | { 79 | return m_path.curveArray()[m_index - 1].get(); 80 | } 81 | return nullptr; 82 | } 83 | 84 | const Curve * Segment::curveOut() const 85 | { 86 | return const_cast(this)->curveOut(); 87 | } 88 | 89 | Curve * Segment::curveOut() 90 | { 91 | if (m_index == m_path.segmentArray().count() - 1) 92 | { 93 | if (!m_path.isClosed()) 94 | return nullptr; 95 | else 96 | return m_path.curveArray().last().get(); 97 | } 98 | else 99 | { 100 | return m_path.curveArray()[m_index].get(); 101 | } 102 | return nullptr; 103 | } 104 | 105 | bool Segment::isLinear() const 106 | { 107 | if (crunch::isClose(m_handleIn, crunch::Vec2f(0.0), detail::PaperConstants::tolerance()) && 108 | crunch::isClose(m_handleOut, crunch::Vec2f(0.0), detail::PaperConstants::tolerance())) 109 | return true; 110 | 111 | return false; 112 | } 113 | 114 | void Segment::remove() 115 | { 116 | m_path.removeSegment(m_index); 117 | } 118 | 119 | void Segment::transform(const Mat3f & _transform) 120 | { 121 | //@TODO remove all of these isnan asserts eventually? :D 122 | STICK_ASSERT(!std::isnan(m_handleIn.x)); 123 | STICK_ASSERT(!std::isnan(m_handleIn.y)); 124 | STICK_ASSERT(!std::isnan(m_handleOut.x)); 125 | STICK_ASSERT(!std::isnan(m_handleOut.y)); 126 | STICK_ASSERT(!std::isnan(m_position.x)); 127 | STICK_ASSERT(!std::isnan(m_position.y)); 128 | 129 | STICK_ASSERT(!std::isnan(_transform.element(0, 0))); 130 | STICK_ASSERT(!std::isnan(_transform.element(0, 1))); 131 | STICK_ASSERT(!std::isnan(_transform.element(0, 2))); 132 | 133 | STICK_ASSERT(!std::isnan(_transform.element(1, 0))); 134 | STICK_ASSERT(!std::isnan(_transform.element(1, 1))); 135 | STICK_ASSERT(!std::isnan(_transform.element(1, 2))); 136 | 137 | STICK_ASSERT(!std::isnan(_transform.element(2, 0))); 138 | STICK_ASSERT(!std::isnan(_transform.element(2, 1))); 139 | STICK_ASSERT(!std::isnan(_transform.element(2, 2))); 140 | 141 | m_handleIn = _transform * (m_position + m_handleIn); 142 | m_handleOut = _transform * (m_position + m_handleOut); 143 | 144 | STICK_ASSERT(!std::isnan(m_handleIn.x)); 145 | STICK_ASSERT(!std::isnan(m_handleIn.y)); 146 | STICK_ASSERT(!std::isnan(m_handleOut.x)); 147 | STICK_ASSERT(!std::isnan(m_handleOut.y)); 148 | STICK_ASSERT(!std::isnan(m_position.x)); 149 | STICK_ASSERT(!std::isnan(m_position.y)); 150 | 151 | m_position = _transform * m_position; 152 | m_handleIn -= m_position; 153 | m_handleOut -= m_position; 154 | 155 | STICK_ASSERT(!std::isnan(m_handleIn.x)); 156 | STICK_ASSERT(!std::isnan(m_handleIn.y)); 157 | STICK_ASSERT(!std::isnan(m_handleOut.x)); 158 | STICK_ASSERT(!std::isnan(m_handleOut.y)); 159 | STICK_ASSERT(!std::isnan(m_position.x)); 160 | STICK_ASSERT(!std::isnan(m_position.y)); 161 | 162 | Curve * cin = curveIn(); 163 | Curve * cout = curveOut(); 164 | if (cin) 165 | cin->markDirty(); 166 | if (cout) 167 | cout->markDirty(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /Paper/Segment.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_SEGMENT_HPP 2 | #define PAPER_SEGMENT_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | class Path; 9 | class Curve; 10 | 11 | class STICK_API Segment 12 | { 13 | friend class Path; 14 | friend class Curve; 15 | 16 | public: 17 | 18 | Segment(const Path & _path, const Vec2f & _pos, const Vec2f & _handleIn, const Vec2f & _handleOut, stick::Size _idx); 19 | 20 | void setPosition(const Vec2f & _pos); 21 | 22 | void setHandleIn(const Vec2f & _pos); 23 | 24 | void setHandleOut(const Vec2f & _pos); 25 | 26 | const Vec2f & position() const; 27 | 28 | const Vec2f & handleIn() const; 29 | 30 | const Vec2f & handleOut() const; 31 | 32 | Vec2f handleInAbsolute() const; 33 | 34 | Vec2f handleOutAbsolute() const; 35 | 36 | Curve * curveIn(); 37 | 38 | const Curve * curveIn() const; 39 | 40 | Curve * curveOut(); 41 | 42 | const Curve * curveOut() const; 43 | 44 | bool isLinear() const; 45 | 46 | void remove(); 47 | 48 | 49 | private: 50 | 51 | void transform(const Mat3f & _transform); 52 | 53 | 54 | Path m_path; 55 | Vec2f m_position; 56 | Vec2f m_handleIn; 57 | Vec2f m_handleOut; 58 | stick::Size m_index; 59 | }; 60 | } 61 | 62 | #endif //PAPER_SEGMENT_HPP 63 | -------------------------------------------------------------------------------- /Paper/Symbol.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | namespace paper 6 | { 7 | Symbol::Symbol() 8 | { 9 | 10 | } 11 | 12 | PlacedSymbol Symbol::place(const Vec2f & _position) 13 | { 14 | STICK_ASSERT(isValid()); 15 | STICK_ASSERT(hasComponent()); 16 | Document doc = get(); 17 | PlacedSymbol ret = brick::reinterpretEntity(doc.hub().createEntity()); 18 | ret.set(*this); 19 | ret.set(EntityType::PlacedSymbol); 20 | ret.set(comps::BoundsData{true, Rect(0, 0, 0, 0)}); 21 | ret.set(comps::BoundsData{true, Rect(0, 0, 0, 0)}); 22 | ret.set(comps::BoundsData{true, Rect(0, 0, 0, 0)}); 23 | ret.set(comps::BoundsData{true, Rect(0, 0, 0, 0)}); 24 | 25 | if (!hasComponent()) 26 | set(PlacedSymbolArray()); 27 | 28 | PlacedSymbolArray & ps = get(); 29 | ps.append(ret); 30 | 31 | doc.addChild(ret); 32 | ret.translateTransform(_position); 33 | 34 | return ret; 35 | } 36 | 37 | void Symbol::remove() 38 | { 39 | // remove all placed symbols that use this symbol 40 | for (PlacedSymbol & s : get()) 41 | { 42 | s.remove(); 43 | } 44 | //free all components and invalidate this entity handle 45 | destroy(); 46 | } 47 | 48 | Item Symbol::item() const 49 | { 50 | if (hasComponent()) 51 | return get(); 52 | return Item(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /Paper/Symbol.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_SYMBOL_HPP 2 | #define PAPER_SYMBOL_HPP 3 | 4 | #include 5 | #include 6 | 7 | namespace paper 8 | { 9 | class PlacedSymbol; 10 | class Document; 11 | using PlacedSymbolArray = stick::DynamicArray; 12 | 13 | namespace comps 14 | { 15 | //symbol components 16 | using ReferencedItem = brick::Component; 17 | using PlacedSymbols = brick::Component; 18 | } 19 | 20 | //Symbol is not an item as it lives outside of the DOM 21 | class STICK_API Symbol : public brick::TypedEntity 22 | { 23 | public: 24 | 25 | Symbol(); 26 | 27 | PlacedSymbol place(const Vec2f & _position); 28 | 29 | void remove(); 30 | 31 | Item item() const; 32 | }; 33 | } 34 | 35 | #endif //PAPER_SYMBOL_HPP 36 | -------------------------------------------------------------------------------- /Paper/Tarp/TarpRenderer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #define TARP_IMPLEMENTATION_OPENGL 6 | #include 7 | 8 | namespace paper 9 | { 10 | namespace tarp 11 | { 12 | using namespace stick; 13 | 14 | using tpSegmentArray = stick::DynamicArray; 15 | 16 | namespace detail 17 | { 18 | struct TarpStuff 19 | { 20 | tpContext ctx; 21 | tpStyle style; 22 | //@TODO: allow to pass in an allocator for this 23 | tpSegmentArray tmpSegmentBuffer; 24 | }; 25 | 26 | struct TarpRenderData 27 | { 28 | TarpRenderData() 29 | { 30 | path = tpPathInvalidHandle(); 31 | } 32 | 33 | ~TarpRenderData() 34 | { 35 | tpPathDestroy(path); 36 | } 37 | 38 | tpPath path; 39 | }; 40 | 41 | struct TarpGradientData 42 | { 43 | TarpGradientData() 44 | { 45 | gradient = tpGradientInvalidHandle(); 46 | } 47 | 48 | ~TarpGradientData() 49 | { 50 | tpGradientDestroy(gradient); 51 | } 52 | 53 | tpGradient gradient; 54 | }; 55 | } 56 | 57 | namespace comps 58 | { 59 | using TarpRenderData = brick::Component; 60 | using TarpGradientData = brick::Component; 61 | } 62 | 63 | struct Gl3wInitializer 64 | { 65 | Gl3wInitializer() : 66 | bError(true) 67 | { 68 | bError = gl3wInit(); 69 | } 70 | 71 | bool bError; 72 | }; 73 | 74 | static bool ensureGl3w() 75 | { 76 | static Gl3wInitializer s_initializer; 77 | STICK_ASSERT(!s_initializer.bError); 78 | return s_initializer.bError; 79 | } 80 | 81 | TarpRenderer::TarpRenderer() 82 | { 83 | 84 | } 85 | 86 | TarpRenderer::~TarpRenderer() 87 | { 88 | if (m_tarp) 89 | { 90 | tpStyleDestroy(m_tarp->style); 91 | tpContextDestroy(m_tarp->ctx); 92 | } 93 | } 94 | 95 | Error TarpRenderer::init(Document _doc) 96 | { 97 | if (ensureGl3w()) 98 | { 99 | //@TODO: Better error code 100 | return Error(ec::InvalidOperation, "Could not initialize opengl", STICK_FILE, STICK_LINE); 101 | } 102 | 103 | m_tarp = makeUnique(); 104 | 105 | m_tarp->ctx = tpContextCreate(); 106 | if (!tpContextIsValidHandle(m_tarp->ctx)) 107 | { 108 | return Error(ec::InvalidOperation, 109 | String::formatted("Could not init Tarp context: %s\n", 110 | tpContextErrorMessage(m_tarp->ctx)), 111 | STICK_FILE, STICK_LINE); 112 | } 113 | 114 | m_tarp->style = tpStyleCreate(); 115 | 116 | this->m_document = _doc; 117 | 118 | return Error(); 119 | } 120 | 121 | void TarpRenderer::setViewport(Float _x, Float _y, Float _widthInPixels, Float _heightInPixels) 122 | { 123 | m_viewport = Rect(_x, _y, _x + _widthInPixels, _y + _heightInPixels); 124 | } 125 | 126 | void TarpRenderer::setProjection(const Mat4f & _projection) 127 | { 128 | tpSetProjection(m_tarp->ctx, (const tpMat4 *)&_projection); 129 | } 130 | 131 | void TarpRenderer::reserveItems(Size _count) 132 | { 133 | if (m_document) 134 | { 135 | m_document.reserveItems(_count); 136 | } 137 | } 138 | 139 | static detail::TarpRenderData & ensureRenderData(Path _path) 140 | { 141 | auto & ret = _path.ensureComponent(); 142 | if (!tpPathIsValidHandle(ret.path)) 143 | ret.path = tpPathCreate(); 144 | 145 | return ret; 146 | } 147 | 148 | static void toTarpSegments(tpSegmentArray & _tmpData, Path _path, const Mat3f * _transform) 149 | { 150 | _tmpData.clear(); 151 | if (!_transform) 152 | { 153 | for (auto & seg : _path.segments()) 154 | { 155 | Vec2f hi = seg.handleInAbsolute(); 156 | Vec2f ho = seg.handleOutAbsolute(); 157 | _tmpData.append((tpSegment) 158 | { 159 | {hi.x, hi.y}, 160 | {seg.position().x, seg.position().y}, 161 | {ho.x, ho.y} 162 | }); 163 | } 164 | } 165 | else 166 | { 167 | //tarp does not support per contour transforms, so we need to bring child paths segments 168 | //to path space before adding it as a contour! 169 | for (auto & seg : _path.segments()) 170 | { 171 | Vec2f hi = *_transform * seg.handleInAbsolute(); 172 | Vec2f pos = *_transform * seg.position(); 173 | Vec2f ho = *_transform * seg.handleOutAbsolute(); 174 | _tmpData.append((tpSegment) 175 | { 176 | {hi.x, hi.y}, 177 | {pos.x, pos.y}, 178 | {ho.x, ho.y} 179 | }); 180 | } 181 | } 182 | } 183 | 184 | static void recursivelyUpdateTarpPath(tpSegmentArray & _tmpData, Path _path, tpPath _tarpPath, const Mat3f * _transform, UInt32 & _contourIndex) 185 | { 186 | if (_path.hasComponent()) 187 | { 188 | _path.removeComponent(); 189 | 190 | toTarpSegments(_tmpData, _path, _transform); 191 | tpPathSetContour(_tarpPath, _contourIndex, &_tmpData[0], _tmpData.count(), (tpBool)_path.isClosed()); 192 | } 193 | 194 | _contourIndex += 1; 195 | 196 | for (auto & c : _path.children()) 197 | { 198 | STICK_ASSERT(c.itemType() == EntityType::Path); 199 | Path p = brick::reinterpretEntity(c); 200 | 201 | const Mat3f * t = _transform; 202 | Mat3f tmp; 203 | if (p.hasTransform()) 204 | { 205 | if (_transform) 206 | { 207 | tmp = *_transform * p.transform(); 208 | t = &tmp; 209 | } 210 | else 211 | { 212 | t = &p.transform(); 213 | } 214 | } 215 | 216 | recursivelyUpdateTarpPath(_tmpData, p, _tarpPath, t, _contourIndex); 217 | } 218 | } 219 | 220 | static void updateTarpPath(tpSegmentArray & _tmpData, Path _path, tpPath _tarpPath, const Mat3f * _transform) 221 | { 222 | UInt32 contourIndex = 0; 223 | recursivelyUpdateTarpPath(_tmpData, _path, _tarpPath, _transform, contourIndex); 224 | 225 | // remove contours that are not used anymore 226 | if (contourIndex < tpPathContourCount(_tarpPath)) 227 | { 228 | for (Size i = tpPathContourCount(_tarpPath) - 1; i >= contourIndex; --i) 229 | { 230 | tpPathRemoveContour(_tarpPath, i); 231 | } 232 | } 233 | } 234 | 235 | static detail::TarpGradientData & updateTarpGradient(BaseGradient _grad) 236 | { 237 | detail::TarpGradientData & gd = _grad.ensureComponent(); 238 | if (!tpGradientIsValidHandle(gd.gradient)) 239 | { 240 | gd.gradient = tpGradientCreateLinear(0, 0, 0, 0); 241 | } 242 | 243 | auto & dfs = _grad.get(); 244 | if (dfs.bPositionsDirty) 245 | { 246 | dfs.bPositionsDirty = false; 247 | tpGradientSetPositions(gd.gradient, _grad.origin().x, _grad.origin().y, _grad.destination().x, _grad.destination().y); 248 | } 249 | if (dfs.bStopsDirty) 250 | { 251 | dfs.bStopsDirty = false; 252 | tpGradientClearColorStops(gd.gradient); 253 | for (auto & stop : _grad.stops()) 254 | { 255 | tpGradientAddColorStop(gd.gradient, stop.color.r, stop.color.g, stop.color.b, stop.color.a, stop.offset); 256 | } 257 | } 258 | return gd; 259 | } 260 | 261 | Error TarpRenderer::drawPath(Path _path, const Mat3f & _transform) 262 | { 263 | detail::TarpRenderData & rd = ensureRenderData(_path); 264 | 265 | if (_path.fill().is()) 266 | { 267 | ColorRGBA & col = _path.fill().get(); 268 | tpStyleSetFillColor(m_tarp->style, col.r, col.g, col.b, col.a); 269 | tpStyleSetFillRule(m_tarp->style, _path.windingRule() == WindingRule::NonZero ? kTpFillRuleNonZero : kTpFillRuleEvenOdd); 270 | } 271 | else if (_path.fill().is()) 272 | { 273 | detail::TarpGradientData & gd = updateTarpGradient(_path.fill().get()); 274 | tpStyleSetFillGradient(m_tarp->style, gd.gradient); 275 | } 276 | else 277 | { 278 | tpStyleRemoveFill(m_tarp->style); 279 | } 280 | 281 | if (!_path.stroke().is()) 282 | { 283 | tpStyleSetStrokeWidth(m_tarp->style, _path.strokeWidth()); 284 | if (_path.stroke().is()) 285 | { 286 | ColorRGBA & col = _path.stroke().get(); 287 | tpStyleSetStrokeColor(m_tarp->style, col.r, col.g, col.b, col.a); 288 | } 289 | else if (_path.stroke().is()) 290 | { 291 | detail::TarpGradientData & gd = updateTarpGradient(_path.stroke().get()); 292 | tpStyleSetStrokeGradient(m_tarp->style, gd.gradient); 293 | } 294 | 295 | tpStyleSetMiterLimit(m_tarp->style, _path.miterLimit()); 296 | 297 | switch (_path.strokeJoin()) 298 | { 299 | case StrokeJoin::Round: 300 | tpStyleSetStrokeJoin(m_tarp->style, kTpStrokeJoinRound); 301 | break; 302 | case StrokeJoin::Miter: 303 | tpStyleSetStrokeJoin(m_tarp->style, kTpStrokeJoinMiter); 304 | break; 305 | case StrokeJoin::Bevel: 306 | default: 307 | tpStyleSetStrokeJoin(m_tarp->style, kTpStrokeJoinBevel); 308 | break; 309 | } 310 | 311 | switch (_path.strokeCap()) 312 | { 313 | case StrokeCap::Round: 314 | tpStyleSetStrokeCap(m_tarp->style, kTpStrokeCapRound); 315 | break; 316 | case StrokeCap::Square: 317 | tpStyleSetStrokeCap(m_tarp->style, kTpStrokeCapSquare); 318 | break; 319 | case StrokeCap::Butt: 320 | default: 321 | tpStyleSetStrokeCap(m_tarp->style, kTpStrokeCapButt); 322 | break; 323 | } 324 | 325 | auto & da = _path.dashArray(); 326 | if (da.count()) 327 | { 328 | tpStyleSetDashArray(m_tarp->style, &da[0], da.count()); 329 | tpStyleSetDashOffset(m_tarp->style, _path.dashOffset()); 330 | } 331 | else 332 | { 333 | tpStyleSetDashArray(m_tarp->style, NULL, 0); 334 | } 335 | } 336 | else 337 | { 338 | tpStyleRemoveStroke(m_tarp->style); 339 | } 340 | 341 | // tpPathClear(rd.path); 342 | // recursivelyAddContours(m_tarp->tmpSegmentBuffer, _path, rd.path, nullptr); 343 | updateTarpPath(m_tarp->tmpSegmentBuffer, _path, rd.path, nullptr); 344 | 345 | // printf("%f", _transform[0][0]); 346 | 347 | tpTransform trans = tpTransformMake(_transform[0][0], _transform[1][0], _transform[2][0], 348 | _transform[0][1], _transform[1][1], _transform[2][1]); 349 | 350 | tpSetTransform(m_tarp->ctx, &trans); 351 | tpBool err = tpDrawPath(m_tarp->ctx, rd.path, m_tarp->style); 352 | 353 | if (err) return Error(ec::InvalidOperation, "Failed to draw tarp path", STICK_FILE, STICK_LINE); 354 | return Error(); 355 | } 356 | 357 | Error TarpRenderer::beginClipping(Path _clippingPath, const Mat3f & _transform) 358 | { 359 | detail::TarpRenderData & rd = ensureRenderData(_clippingPath); 360 | 361 | updateTarpPath(m_tarp->tmpSegmentBuffer, _clippingPath, rd.path, nullptr); 362 | 363 | tpTransform trans = tpTransformMake(_transform[0][0], _transform[1][0], _transform[2][0], 364 | _transform[0][1], _transform[1][1], _transform[2][1]); 365 | 366 | tpSetTransform(m_tarp->ctx, &trans); 367 | tpBool err = tpBeginClipping(m_tarp->ctx, rd.path); 368 | if (err) return Error(ec::InvalidOperation, "Failed to draw tarp clip path", STICK_FILE, STICK_LINE); 369 | return Error(); 370 | } 371 | 372 | Error TarpRenderer::endClipping() 373 | { 374 | tpBool err = tpEndClipping(m_tarp->ctx); 375 | if (err) return Error(ec::InvalidOperation, "Failed to draw tarp clip path", STICK_FILE, STICK_LINE); 376 | return Error(); 377 | } 378 | 379 | Error TarpRenderer::prepareDrawing() 380 | { 381 | glViewport(m_viewport.min().x, m_viewport.min().y, m_viewport.width(), m_viewport.height()); 382 | tpPrepareDrawing(m_tarp->ctx); 383 | 384 | return Error(); 385 | } 386 | 387 | Error TarpRenderer::finishDrawing() 388 | { 389 | tpFinishDrawing(m_tarp->ctx); 390 | return Error(); 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /Paper/Tarp/TarpRenderer.hpp: -------------------------------------------------------------------------------- 1 | #ifndef PAPER_TARP_TARPRENDERER_HPP 2 | #define PAPER_TARP_TARPRENDERER_HPP 3 | 4 | #include 5 | 6 | namespace paper 7 | { 8 | namespace tarp 9 | { 10 | namespace detail 11 | { 12 | struct TarpStuff; 13 | }; 14 | 15 | class STICK_API TarpRenderer : public RenderInterface 16 | { 17 | public: 18 | 19 | TarpRenderer(); 20 | 21 | ~TarpRenderer(); 22 | 23 | 24 | stick::Error init(Document _doc) final; 25 | 26 | void setViewport(Float _x, Float _y, Float _widthInPixels, Float _heightInPixels) final; 27 | 28 | void setProjection(const Mat4f & _projection) final; 29 | 30 | void reserveItems(stick::Size _count) final; 31 | 32 | stick::Error drawPath(Path _path, const Mat3f & _transform) final; 33 | 34 | stick::Error beginClipping(Path _clippingPath, const Mat3f & _transform) final; 35 | 36 | stick::Error endClipping() final; 37 | 38 | stick::Error prepareDrawing() final; 39 | 40 | stick::Error finishDrawing() final; 41 | 42 | 43 | private: 44 | 45 | stick::UniquePtr m_tarp; 46 | Rect m_viewport; 47 | }; 48 | } 49 | } 50 | 51 | #endif //PAPER_TARP_TARPRENDERER_HPP 52 | 53 | //@TODO: Ideas for future rendering improvements: 54 | //1. Render all consecutive static paths onto one layer. 55 | //2. Render animated paths onto their own layers. 56 | //3. Composite all layers. 57 | //======================================================= 58 | -------------------------------------------------------------------------------- /Playground/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(PkgConfig REQUIRED) 2 | find_package(OpenGL REQUIRED) 3 | pkg_search_module(GLFW REQUIRED glfw3) 4 | 5 | include_directories (${OPENGL_INCLUDE_DIRS} ${GLFW_INCLUDE_DIRS}) 6 | add_executable (PaperPlayground PaperPlayground.cpp) 7 | target_link_libraries(PaperPlayground Paper ${PAPERDEPS} ${GLFW_STATIC_LIBRARIES} ${OPENGL_LIBRARIES}) -------------------------------------------------------------------------------- /Playground/PaperPlayground.cpp: -------------------------------------------------------------------------------- 1 | // This example shows the very basic steps to create a Paper Document 2 | // and draw it. 3 | 4 | // we use GLFW to open a simple window 5 | #include 6 | 7 | //include some paper headers. 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | 20 | //we want to use the paper, brick, crunch & stick namespaces 21 | using namespace paper; // paper namespace 22 | using namespace brick; // brick namespace for entity / component things 23 | using namespace crunch; // crunch namespace for math 24 | using namespace stick; // stick namespace for core data structures/containers etc. 25 | 26 | int main(int _argc, const char * _args[]) 27 | { 28 | // initialize glfw 29 | if (!glfwInit()) 30 | return EXIT_FAILURE; 31 | 32 | // and set some hints to get the correct opengl versions/profiles 33 | glfwWindowHint(GLFW_SAMPLES, 8); 34 | glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); 35 | glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); 36 | glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); 37 | glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); 38 | 39 | //create the window 40 | GLFWwindow * window = glfwCreateWindow(800, 600, "Hello Paper Example", NULL, NULL); 41 | if (window) 42 | { 43 | glfwMakeContextCurrent(window); 44 | 45 | // the brick entity hub to use by the paper document. 46 | Hub hub; 47 | // create the document. 48 | Document doc = createDocument(hub); 49 | doc.setSize(800, 600); 50 | // create the opengl renderer for the document 51 | tarp::TarpRenderer renderer; 52 | auto err = renderer.init(doc); 53 | if (err) 54 | { 55 | printf("Error %s\n", err.message().cString()); 56 | return EXIT_FAILURE; 57 | } 58 | 59 | 60 | Randomizer r; 61 | 62 | LinearGradient grad = doc.createLinearGradient(Vec2f(r.randomf(100, 700), r.randomf(100, 500)), 63 | Vec2f(r.randomf(100, 700), r.randomf(100, 500))); 64 | 65 | for (int i = 0; i < r.randomi(2, 10); ++i) 66 | { 67 | grad.addStop(ColorRGBA(r.randomf(0, 1), r.randomf(0, 1), r.randomf(0, 1), 1.0), r.randomf(0, 1)); 68 | } 69 | 70 | Path circle = doc.createCircle(Vec2f(400, 300), 200); 71 | circle.setName("A"); 72 | circle.setFill(ColorRGBA(1, 0, 0, 1)); 73 | circle.setStroke(ColorRGBA(0.0, 1.0, 1.0, 1.0)); 74 | circle.setStrokeWidth(10.0); 75 | circle.setStroke(grad); 76 | 77 | Path circle2 = doc.createCircle(Vec2f(400, 300), 20); 78 | circle2.setName("B"); 79 | circle2.translateTransform(10, 10); 80 | circle.addChild(circle2); 81 | 82 | Path circle3 = doc.createCircle(Vec2f(400, 300), 10); 83 | circle3.setName("C"); 84 | circle3.translateTransform(-100, -100); 85 | circle2.addChild(circle3); 86 | 87 | /*Path rct = doc.createRectangle(Vec2f(350, 250), Vec2f(450, 350)); 88 | rct.setFill(ColorRGBA(1.0, 1.0, 1.0, 1.0)); 89 | 90 | Group grp = doc.createGroup(); 91 | grp.addChild(rct); 92 | grp.addChild(circle); 93 | grp.setClipped(true);*/ 94 | 95 | int counter = 0; 96 | 97 | // the main loop 98 | while (!glfwWindowShouldClose(window)) 99 | { 100 | // clear the background to black 101 | glClearColor(0, 0, 0, 1); 102 | glClear(GL_COLOR_BUFFER_BIT); 103 | 104 | // get the window size from the glfw window 105 | // and set it as the viewport on the renderer 106 | int width, height; 107 | glfwGetFramebufferSize(window, &width, &height); 108 | renderer.setViewport(0, 0, width, height); 109 | 110 | glfwGetWindowSize(window, &width, &height); 111 | renderer.setProjection(Mat4f::ortho(0, width, height, 0, -1, 1)); 112 | // renderer.setTransform(Mat3f::identity()); 113 | 114 | auto err = renderer.draw(); 115 | if (err) 116 | { 117 | printf("ERROR: %s\n", err.message().cString()); 118 | return EXIT_FAILURE; 119 | } 120 | 121 | // counter++; 122 | // if(counter > 200) 123 | // { 124 | // // printf("CC %lu\n", circle2.children().count()); 125 | // circle.removeChild(circle2); 126 | // // circle3.remove(); 127 | // } 128 | 129 | glfwSwapBuffers(window); 130 | glfwPollEvents(); 131 | } 132 | } 133 | else 134 | { 135 | glfwTerminate(); 136 | printf("Could not open GLFW window :(\n"); 137 | return EXIT_FAILURE; 138 | } 139 | 140 | // clean up glfw 141 | glfwDestroyWindow(window); 142 | glfwTerminate(); 143 | 144 | return EXIT_SUCCESS; 145 | } 146 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Paper [![Build Status](https://travis-ci.org/mokafolio/Paper.svg?branch=master)](https://travis-ci.org/mokafolio/Paper) 2 | ========== 3 | 4 | Overview 5 | ---------- 6 | 7 | *Paper* is a C++ port of [paper.js](http://paperjs.org/). 8 | 9 | Supported Platforms 10 | ---------- 11 | *OS X* and *Linux* at this point. 12 | 13 | Dependencies 14 | ---------- 15 | 16 | - [CMake](https://cmake.org/) for cross platform building. 17 | - [Stick](https://github.com/mokafolio/Stick) for data structures, containers, allocators etc. 18 | - [Crunch](https://github.com/mokafolio/Crunch) for math. 19 | - [Brick](https://github.com/mokafolio/Brick) for entity/component things that are used to represent the DOM. 20 | - [Scrub](https://github.com/mokafolio/Scrub) for xml/json parsing and composing. 21 | 22 | 23 | Installation 24 | ---------- 25 | 26 | ###RECOMMENDED 27 | 28 | Since we are still in pre alpha, the brew tap does not get updated that often, while the master repository will be. 29 | You will most likely get the best experience/stability and features out of simply cloning the current master branch at this point. 30 | If you manually installed the dependencies you can simply build and install *Paper* by doing: 31 | 32 | ``` 33 | mkdir build 34 | cd build 35 | cmake .. 36 | make install 37 | ``` 38 | 39 | If you want to install the dependencies through the git submodules, do this: 40 | 41 | ``` 42 | git submodule init 43 | git submodule update 44 | mkdir build 45 | cd build 46 | cmake -DBuildSubmodules=On .. 47 | make install 48 | ``` 49 | 50 | 51 | ###OS X 52 | 53 | The easiest way to install *Paper* on *OS X* is to use [Homebrew](http://brew.sh/). 54 | If you installed *Homebrew*, hook into [this](https://github.com/mokafolio/homebrew-mokatap) custom tap via: 55 | `brew tap mokafolio/mokatap` 56 | Then run the following to install *Paper* and all its dependencies. 57 | `brew install paper` 58 | 59 | Examples 60 | --------- 61 | Paper examples are located [here](https://github.com/mokafolio/PaperExamples). A lot more coming soonish. 62 | 63 | License 64 | ---------- 65 | MIT License 66 | 67 | Differences to paper.js 68 | ---------- 69 | Coming soon. 70 | 71 | TODO 72 | ---------- 73 | - make sure the Allocator's are actually used for memory allocation 74 | - add path intersections 75 | - add path splitting 76 | - add boolean operations 77 | - gradients 78 | - different blend modes 79 | - shadows 80 | - A lot more unit tests (specifically for numeric stability). 81 | -------------------------------------------------------------------------------- /Tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable (PaperTests PaperTests.cpp) 2 | target_link_libraries(PaperTests Paper ${PAPERDEPS}) 3 | add_custom_target(check COMMAND PaperTests) 4 | -------------------------------------------------------------------------------- /Tests/PaperTests.cpp: -------------------------------------------------------------------------------- 1 | //#include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | // #include 9 | 10 | using namespace stick; 11 | using namespace brick; 12 | using namespace paper; 13 | using namespace crunch; 14 | 15 | const Suite spec[] = 16 | { 17 | SUITE("DOM Tests") 18 | { 19 | Document doc = createDocument(); 20 | 21 | //we just throw in some reserve calls... 22 | doc.reserveItems<>(1000); 23 | 24 | Group grp = doc.createGroup("Group"); 25 | EXPECT(doc.children().count() == 1); 26 | EXPECT(grp.name() == "Group"); 27 | EXPECT(grp.parent() == doc); 28 | EXPECT(grp.document() == doc); 29 | 30 | Group grp2 = doc.createGroup("Group2"); 31 | EXPECT(grp2.parent() == doc); 32 | EXPECT(doc.children().count() == 2); 33 | grp.addChild(grp2); 34 | EXPECT(grp2.parent() == grp); 35 | EXPECT(doc.children().count() == 1); 36 | 37 | Group grp3 = doc.createGroup("Group3"); 38 | Group grp4 = doc.createGroup("Group4"); 39 | grp.addChild(grp4); 40 | grp3.insertBelow(grp2); 41 | EXPECT(grp.children().count() == 3); 42 | EXPECT(grp.children()[0] == grp3); 43 | EXPECT(grp.children()[1] == grp2); 44 | EXPECT(grp.children()[2] == grp4); 45 | grp3.insertAbove(grp4); 46 | EXPECT(grp.children().count() == 3); 47 | EXPECT(grp.children()[0] == grp2); 48 | EXPECT(grp.children()[1] == grp4); 49 | EXPECT(grp.children()[2] == grp3); 50 | EXPECT(grp3.parent() == grp); 51 | 52 | //...to see if that fucks anything up 53 | doc.reserveItems<>(2000); 54 | 55 | grp2.insertAbove(grp4); 56 | EXPECT(grp.children()[0] == grp4); 57 | EXPECT(grp.children()[1] == grp2); 58 | EXPECT(grp.children()[2] == grp3); 59 | grp2.sendToFront(); 60 | EXPECT(grp.children()[0] == grp4); 61 | EXPECT(grp.children()[1] == grp3); 62 | EXPECT(grp.children()[2] == grp2); 63 | grp2.sendToBack(); 64 | EXPECT(grp.children()[0] == grp2); 65 | EXPECT(grp.children()[1] == grp4); 66 | EXPECT(grp.children()[2] == grp3); 67 | 68 | grp2.insertAbove(grp); 69 | EXPECT(grp.children().count() == 2); 70 | EXPECT(doc.children().count() == 2); 71 | EXPECT(grp2.parent() == doc); 72 | grp2.remove(); 73 | EXPECT(!grp2.isValid()); 74 | EXPECT(doc.children().count() == 1); 75 | }, 76 | SUITE("Basic Path Tests") 77 | { 78 | Document doc = createDocument(); 79 | 80 | Path p = doc.createPath("test"); 81 | p.addPoint(Vec2f(100.0f, 30.0f)); 82 | p.addPoint(Vec2f(200.0f, 30.0f)); 83 | EXPECT(p.segmentArray().count() == 2); 84 | EXPECT(p.segmentArray()[0]->position() == Vec2f(100.0f, 30.0f)); 85 | EXPECT(p.segmentArray()[1]->position() == Vec2f(200.0f, 30.0f)); 86 | EXPECT(p.isPolygon()); 87 | 88 | // using SegmentView = paper::detail::ContainerView>, Segment>; 89 | // SegmentView view(p.segmentArray().begin(), p.segmentArray().end()); 90 | // for(const Segment & seg : view) 91 | // { 92 | // printf("Segment pos: %s\n", crunch::toString(seg.position()).cString()); 93 | // } 94 | 95 | p.addSegment(Vec2f(150.0f, 150.0f), Vec2f(-5.0f, -3.0f), Vec2f(5.0f, 3.0f)); 96 | EXPECT(p.segmentArray().count() == 3); 97 | EXPECT(p.segmentArray()[2]->position() == Vec2f(150.0f, 150.0f)); 98 | EXPECT(p.segmentArray()[2]->handleIn() == Vec2f(-5.0f, -3.0f)); 99 | EXPECT(p.segmentArray()[2]->handleOut() == Vec2f(5.0f, 3.0f)); 100 | 101 | EXPECT(p.curveArray().count() == 2); 102 | EXPECT(!p.isPolygon()); 103 | EXPECT(!p.isClosed()); 104 | 105 | stick::DynamicArray expectedCurves = 106 | { 107 | Vec2f(100.0f, 30.0f), Vec2f(0.0f, 0.0f), Vec2f(0.0f, 0.0f), Vec2f(200.0f, 30.0f), 108 | Vec2f(200.0f, 30.0f), Vec2f(0.0f, 0.0f), Vec2f(-5.0f, -3.0f), Vec2f(150.0f, 150.0f) 109 | }; 110 | Size i = 0; 111 | for (const auto & c : p.curves()) 112 | { 113 | EXPECT(c.positionOne() == expectedCurves[i++]); 114 | EXPECT(c.handleOne() == expectedCurves[i++]); 115 | EXPECT(c.handleTwo() == expectedCurves[i++]); 116 | EXPECT(c.positionTwo() == expectedCurves[i++]); 117 | } 118 | 119 | p.closePath(); 120 | EXPECT(p.isClosed()); 121 | EXPECT(p.curveArray().count() == 3); 122 | EXPECT(p.curveArray().last()->positionOne() == Vec2f(150.0f, 150.0f)); 123 | EXPECT(p.curveArray().last()->handleOne() == Vec2f(5.0f, 3.0f)); 124 | EXPECT(p.curveArray().last()->positionTwo() == Vec2f(100.0f, 30.0f)); 125 | 126 | //test insertion 127 | p.insertSegment(1, Vec2f(100, 75.0)); 128 | EXPECT(p.segmentArray()[0]->position() == Vec2f(100.0f, 30.0f)); 129 | EXPECT(p.segmentArray()[1]->position() == Vec2f(100.0f, 75.0f)); 130 | EXPECT(p.segmentArray()[2]->position() == Vec2f(200.0f, 30.0f)); 131 | 132 | EXPECT(p.curveArray().count() == 4); 133 | stick::DynamicArray expectedCurves2 = 134 | { 135 | Vec2f(100.0f, 30.0f), Vec2f(0.0f, 0.0f), Vec2f(0.0f, 0.0f), Vec2f(100.0f, 75.0f), 136 | Vec2f(100.0f, 75.0f), Vec2f(0.0f, 0.0f), Vec2f(0.0f, 0.0f), Vec2f(200.0f, 30.0f), 137 | Vec2f(200.0f, 30.0f), Vec2f(0.0f, 0.0f), Vec2f(-5.0f, -3.0f), Vec2f(150.0f, 150.0f), 138 | Vec2f(150.0f, 150.0f), Vec2f(5.0f, 3.0f), Vec2f(0.0f, 0.0f), Vec2f(100.0f, 30.0f), 139 | }; 140 | i = 0; 141 | for (const auto & c : p.curves()) 142 | { 143 | EXPECT(c.positionOne() == expectedCurves2[i++]); 144 | EXPECT(c.handleOne() == expectedCurves2[i++]); 145 | EXPECT(c.handleTwo() == expectedCurves2[i++]); 146 | EXPECT(c.positionTwo() == expectedCurves2[i++]); 147 | } 148 | 149 | }, 150 | SUITE("Attribute Tests") 151 | { 152 | Document doc = createDocument(); 153 | Path child = doc.createPath(); 154 | EXPECT(!child.hasFill()); 155 | EXPECT(!child.hasStroke()); 156 | child.setFill(ColorRGBA(1.0f, 0.5f, 0.3f, 1.0f)); 157 | child.setStroke(ColorRGBA(1.0f, 0.0f, 0.75f, 1.0f)); 158 | EXPECT(child.fill().get() == ColorRGBA(1.0f, 0.5f, 0.3f, 1.0f)); 159 | EXPECT(child.stroke().get() == ColorRGBA(1.0f, 0.0f, 0.75f, 1.0f)); 160 | EXPECT(child.hasFill()); 161 | EXPECT(child.hasStroke()); 162 | Group grp = doc.createGroup(); 163 | grp.addChild(child); 164 | grp.setFill(ColorRGBA(0.34f, 0.25f, 1.0f, 0.5f)); 165 | EXPECT(grp.fill().get() == ColorRGBA(0.34f, 0.25f, 1.0f, 0.5f)); 166 | EXPECT(child.fill().get() == ColorRGBA(0.34f, 0.25f, 1.0f, 0.5f)); 167 | child.removeFill(); 168 | EXPECT(child.hasFill()); 169 | EXPECT(child.fill().get() == ColorRGBA(0.34f, 0.25f, 1.0f, 0.5f)); 170 | grp.removeFill(); 171 | EXPECT(!child.hasFill()); 172 | EXPECT(!grp.hasFill()); 173 | }, 174 | SUITE("Path Length Tests") 175 | { 176 | Document doc = createDocument(); 177 | Path p = doc.createPath(); 178 | p.addPoint(Vec2f(0.0f, 0.0f)); 179 | p.addPoint(Vec2f(200.0f, 0.0f)); 180 | p.addPoint(Vec2f(200.0f, 200.0f)); 181 | EXPECT(isClose(p.length(), 400.0f)); 182 | 183 | Float rad = 100; 184 | Path p2 = doc.createCircle(Vec2f(0.0f, 0.0f), rad); 185 | Float circumference = Constants::pi() * rad * 2; 186 | printf("%f %f\n", p2.length(), circumference); 187 | EXPECT(isClose(p2.length(), circumference, 0.1f)); 188 | }, 189 | SUITE("Path Orientation Tests") 190 | { 191 | Document doc = createDocument(); 192 | Path p = doc.createPath(); 193 | p.addPoint(Vec2f(0.0f, 0.0f)); 194 | p.addPoint(Vec2f(200.0f, 0.0f)); 195 | p.addPoint(Vec2f(200.0f, 200.0f)); 196 | p.addPoint(Vec2f(0.0f, 200.0f)); 197 | EXPECT(p.isClockwise()); 198 | p.reverse(); 199 | EXPECT(!p.isClockwise()); 200 | }, 201 | SUITE("Path Bounds Tests") 202 | { 203 | Document doc = createDocument(); 204 | Path p = doc.createPath(); 205 | p.addPoint(Vec2f(0.0f, 0.0f)); 206 | p.addPoint(Vec2f(200.0f, 0.0f)); 207 | p.addPoint(Vec2f(200.0f, 100.0f)); 208 | 209 | const Rect & bounds = p.bounds(); 210 | EXPECT(isClose(bounds.min(), Vec2f(0.0f))); 211 | EXPECT(isClose(bounds.width(), 200.0f)); 212 | EXPECT(isClose(bounds.height(), 100.0f)); 213 | 214 | p.addPoint(Vec2f(200.0f, 200.0f)); 215 | const Rect & bounds2 = p.bounds(); 216 | EXPECT(isClose(bounds2.min(), Vec2f(0.0f))); 217 | EXPECT(isClose(bounds2.width(), 200.0f)); 218 | EXPECT(isClose(bounds2.height(), 200.0f)); 219 | 220 | p.closePath(); 221 | p.setStroke(ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); 222 | p.setStrokeWidth(20.0); 223 | p.setStrokeJoin(StrokeJoin::Round); 224 | const Rect & strokeBounds = p.strokeBounds(); 225 | EXPECT(isClose(strokeBounds.min(), Vec2f(-10.0f))); 226 | EXPECT(isClose(strokeBounds.width(), 220.0f)); 227 | EXPECT(isClose(strokeBounds.height(), 220.0f)); 228 | }, 229 | SUITE("Transformed Path Bounds Tests") 230 | { 231 | Document doc = createDocument(); 232 | Path p = doc.createPath(); 233 | p.addPoint(Vec2f(0.0f, 0.0f)); 234 | p.addPoint(Vec2f(100.0f, 0.0f)); 235 | p.addPoint(Vec2f(100.0f, 100.0f)); 236 | p.addPoint(Vec2f(0.0f, 100.0f)); 237 | p.closePath(); 238 | EXPECT(isClose(p.position(), Vec2f(50.0f, 50.0f))); 239 | 240 | p.translateTransform(100, 150); 241 | EXPECT(isClose(p.position(), Vec2f(150.0f, 200.0f))); 242 | const Rect & bounds = p.bounds(); 243 | EXPECT(isClose(bounds.min(), Vec2f(100.0f, 150.0f))); 244 | EXPECT(isClose(bounds.width(), 100.0f)); 245 | EXPECT(isClose(bounds.height(), 100.0f)); 246 | 247 | float diagonal = sqrt(100.0f * 100.0f + 100.0f * 100.0f); 248 | p.rotateTransform(Constants::pi() * 0.25); 249 | const Rect & bounds2 = p.bounds(); 250 | EXPECT(isClose(bounds2.min(), Vec2f(150.0f, 200.0f) - Vec2f(diagonal * 0.5))); 251 | EXPECT(isClose(bounds2.width(), diagonal)); 252 | EXPECT(isClose(bounds2.height(), diagonal)); 253 | 254 | p.scaleTransform(2.0f); 255 | const Rect & bounds4 = p.bounds(); 256 | EXPECT(isClose(bounds4.width(), diagonal * 2)); 257 | EXPECT(isClose(bounds4.height(), diagonal * 2)); 258 | 259 | p.setStroke(ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f)); 260 | p.setStrokeWidth(20.0); 261 | p.setStrokeJoin(StrokeJoin::Round); 262 | p.setStrokeCap(StrokeCap::Round); 263 | const Rect & bounds5 = p.strokeBounds(); 264 | EXPECT(isClose(bounds5.width(), diagonal * 2 + 40.0f)); 265 | EXPECT(isClose(bounds5.height(), diagonal * 2 + 40.0f)); 266 | }, 267 | SUITE("Clone Tests") 268 | { 269 | Document doc = createDocument(); 270 | Group grp = doc.createGroup("grp"); 271 | Path p = doc.createPath("yessaa"); 272 | p.addPoint(Vec2f(100.0f, 30.0f)); 273 | p.addPoint(Vec2f(200.0f, 30.0f)); 274 | p.setStroke(ColorRGBA(1.0f, 0.5f, 0.75f, 0.75f)); 275 | p.setStrokeCap(StrokeCap::Square); 276 | grp.addChild(p); 277 | grp.setFill(ColorRGBA(0.25f, 0.33f, 0.44f, 1.0f)); 278 | grp.setStrokeWidth(10.0); 279 | grp.setStrokeJoin(StrokeJoin::Round); 280 | 281 | printf("WOOOP\n"); 282 | Path p2 = p.clone(); 283 | EXPECT(p2.name() == "yessaa"); 284 | EXPECT(p2.stroke().get() == ColorRGBA(1.0f, 0.5f, 0.75f, 0.75f)); 285 | EXPECT(p2.parent() == grp); 286 | EXPECT(p2.segmentArray().count() == 2); 287 | EXPECT(p2.curveArray().count() == 1); 288 | EXPECT(p2.segmentArray()[0]->position() == Vec2f(100.0f, 30.0f)); 289 | EXPECT(p2.segmentArray()[1]->position() == Vec2f(200.0f, 30.0f)); 290 | p2.set("p2"); 291 | 292 | Group grp2 = grp.clone(); 293 | EXPECT(grp2.name() == "grp"); 294 | EXPECT(grp2.children().count() == 2); 295 | EXPECT(grp2.parent() == doc); 296 | EXPECT(grp2.children()[0].get() == "yessaa"); 297 | EXPECT(grp2.children()[1].get() == "p2"); 298 | 299 | EXPECT(doc.children().count() == 2); 300 | EXPECT(doc.children()[0] == grp); 301 | EXPECT(doc.children()[1] == grp2); 302 | }, 303 | SUITE("SVG Export Tests") 304 | { 305 | //TODO: Turn this into an actual test 306 | Document doc = createDocument(); 307 | doc.translateTransform(Vec2f(100, 200)); 308 | doc.scaleTransform(3.0); 309 | Path c = doc.createCircle(Vec2f(100, 100), 10); 310 | c.setFill(ColorRGBA(1.0, 1.0, 0.0, 1.0)); 311 | auto res = doc.exportSVG(); 312 | printf("%s\n", res.ensure().cString()); 313 | }, 314 | SUITE("SVG Import Tests") 315 | { 316 | { 317 | Document doc = createDocument(); 318 | String svg = ""; 319 | printf("SVG:\n%s\n", svg.cString()); 320 | auto svgdata = doc.parseSVG(svg); 321 | EXPECT(svgdata.width() == 100); 322 | EXPECT(svgdata.height() == 50); 323 | EXPECT(svgdata.group().isValid()); 324 | EXPECT(svgdata.group().children().count() == 1); 325 | EXPECT(Item(svgdata.group().children()[0]).itemType() == EntityType::Path); 326 | Path p = reinterpretEntity(svgdata.group().children()[0]); 327 | EXPECT(p.segmentArray().count() == 3); 328 | EXPECT(isClose(p.segmentArray()[0]->position(), Vec2f(10, 20))); 329 | EXPECT(isClose(p.segmentArray()[1]->position(), Vec2f(100, 20))); 330 | EXPECT(isClose(p.segmentArray()[2]->position(), Vec2f(100, 120))); 331 | EXPECT(p.isClosed()); 332 | } 333 | { 334 | //TODO: test transforms on the group element 335 | Document doc = createDocument(); 336 | String svg = ""; 337 | printf("SVG:\n%s\n", svg.cString()); 338 | auto svgdata = doc.parseSVG(svg); 339 | EXPECT(!svgdata.error()); 340 | EXPECT(svgdata.group().children().count() == 1); 341 | EXPECT(Item(svgdata.group().children()[0]).itemType() == EntityType::Group); 342 | Group grp = reinterpretEntity(svgdata.group().children()[0]); 343 | EXPECT(grp.children().count() == 2); 344 | } 345 | { 346 | //test basic colors and attribute import 347 | Document doc = createDocument(); 348 | String svg = ""; 349 | printf("SVG:\n%s\n", svg.cString()); 350 | auto svgdata = doc.parseSVG(svg); 351 | Path p = reinterpretEntity(svgdata.group().children()[0]); 352 | EXPECT(p.fill().get() == ColorRGBA(1, 0, 0, 1)); 353 | auto s = p.stroke().get(); 354 | EXPECT(isClose(s.r, 51.0f / 255.0f) && isClose(s.g, 51.0f / 255.0f) && isClose(s.b, 51.0f / 255.0f)); 355 | EXPECT(p.strokeWidth() == 2.0f); 356 | Path p2 = reinterpretEntity(svgdata.group().children()[1]); 357 | auto & c2 = p2.fill().get(); 358 | EXPECT(isClose(c2.r, 66.0f / 255.0f) && isClose(c2.g, 134.0f / 255.0f) && isClose(c2.b, 244.0f / 255.0f)); 359 | EXPECT(p2.stroke().get() == ColorRGBA(0, 0, 0, 1)); 360 | EXPECT(p2.isScalingStroke() == false); 361 | EXPECT(isClose(p2.miterLimit(), 33.5f)); 362 | EXPECT(isClose(p2.dashOffset(), 20.33f)); 363 | EXPECT(p2.windingRule() == WindingRule::NonZero); 364 | printf("DA COUNT %lu\n", p2.dashArray().count()); 365 | EXPECT(p2.dashArray().count() == 5); 366 | EXPECT(p2.dashArray()[0] == 1); 367 | EXPECT(p2.dashArray()[1] == 2); 368 | EXPECT(p2.dashArray()[2] == 3); 369 | EXPECT(p2.dashArray()[3] == 4); 370 | EXPECT(p2.dashArray()[4] == 5); 371 | EXPECT(p2.strokeCap() == StrokeCap::Round); 372 | EXPECT(p2.strokeJoin() == StrokeJoin::Miter); 373 | } 374 | }, 375 | SUITE("Basic Intersection Tests") 376 | { 377 | Document doc = createDocument(); 378 | Path circle = doc.createCircle(Vec2f(100, 100), 100); 379 | auto isecs = circle.intersections(); 380 | EXPECT(isecs.count() == 0); 381 | // printf("DA COUNT %lu\n", isecs.count()); 382 | // for (auto & isec : isecs) 383 | // printf("ISEC %f %f\n", isec.position.x, isec.position.y); 384 | 385 | Path line = doc.createPath(); 386 | line.addPoint(Vec2f(-100, 100)); 387 | line.addPoint(Vec2f(300, 100)); 388 | auto isecs2 = line.intersections(circle); 389 | printf("DA COUNT %lu\n", isecs2.count()); 390 | EXPECT(isecs2.count() == 2); 391 | 392 | Path selfIntersectingLinear = doc.createPath(); 393 | selfIntersectingLinear.addPoint(Vec2f(0, 0)); 394 | selfIntersectingLinear.addPoint(Vec2f(100, 0)); 395 | selfIntersectingLinear.addPoint(Vec2f(50, 100)); 396 | selfIntersectingLinear.addPoint(Vec2f(50, -100)); 397 | auto isecs3 = selfIntersectingLinear.intersections(); 398 | EXPECT(isecs3.count() == 1); 399 | EXPECT(crunch::isClose(isecs3[0].position, Vec2f(50, 0))); 400 | 401 | Path selfIntersecting = doc.createPath(); 402 | selfIntersecting.addPoint(Vec2f(100, 100)); 403 | selfIntersecting.arcTo(Vec2f(200, 100)); 404 | selfIntersecting.arcTo(Vec2f(200, 0)); 405 | 406 | auto isecs4 = selfIntersecting.intersections(); 407 | EXPECT(isecs4.count() == 1); 408 | EXPECT(crunch::isClose(isecs4[0].position, Vec2f(150, 50))); 409 | 410 | Path a = doc.createPath(); 411 | a.addPoint(Vec2f(100, 100)); 412 | a.arcTo(Vec2f(200, 100)); 413 | 414 | Path b = doc.createPath(); 415 | b.addPoint(Vec2f(200, 100)); 416 | b.arcTo(Vec2f(200, 0)); 417 | 418 | auto isecs5 = a.intersections(b); 419 | EXPECT(isecs5.count() == 2); 420 | EXPECT(crunch::isClose(isecs5[0].position, Vec2f(150, 50))); 421 | EXPECT(crunch::isClose(isecs5[1].position, Vec2f(200, 100))); 422 | 423 | auto isecs6 = b.intersections(a); 424 | EXPECT(isecs6.count() == 2); 425 | EXPECT(crunch::isClose(isecs6[0].position, Vec2f(200, 100))); 426 | EXPECT(crunch::isClose(isecs6[1].position, Vec2f(150, 50))); 427 | } 428 | }; 429 | 430 | int main(int _argc, const char * _args[]) 431 | { 432 | return runTests(spec, _argc, _args); 433 | } 434 | --------------------------------------------------------------------------------