CPU rendering and SVG parsing uses absolute coordinates.
GPU rendering uses relative coordinates (relative to the bounding box).
The Objects struct stores the absolute bounding boxes now.
Previously it was just using {0,0,1,1} (and thus the GPU's relative coordinates were
equivelant to the CPU's absolute coordinates).
I might have fixed some other things but I can't remember.
#include <unordered_map>
#include <cmath>
+#include <algorithm>
using namespace std;
return Real(BinomialCoeff(n, k)) * Power(u, k) * Power(Real(1.0) - u, n-k);
}
+
+/**
+ * Returns the parametric parameter at the turning point(s)
+ * In one coordinate direction
+ */
+
+static pair<Real, Real> BezierTurningPoints(const Real & p0, const Real & p1, const Real & p2, const Real & p3)
+{
+ // straight line
+ if (p1 == p2 && p2 == p3)
+ {
+ return pair<Real,Real>(0, 1);
+ }
+ Real a = (p1- p0 - 2*(p2-p1) + p3-p2);
+ Real b = (p1-p0 - (p2-p1))*(p1-p0);
+ Real c = (p1-p0);
+ if (a == 0)
+ {
+ if (b == 0)
+ return pair<Real, Real>(0,1);
+ Real t = -c/b;
+ if (t > 1) t = 1;
+ if (t < 0) t = 0;
+ return pair<Real, Real>(t, t);
+ }
+ Debug("a, b, c are %f, %f, %f", Float(a), Float(b), Float(c));
+ if (b*b - 4*a*c < 0)
+ {
+ return pair<Real, Real>(0,1);
+ }
+ pair<Real, Real> tsols = SolveQuadratic(a, b, c);
+ if (tsols.first > 1) tsols.first = 1;
+ if (tsols.first < 0) tsols.first = 0;
+ if (tsols.second > 1) tsols.second = 1;
+ if (tsols.second < 0) tsols.second = 0;
+ return tsols;
}
+
+inline bool CompRealByPtr(const Real * a, const Real * b)
+{
+ return (*a) < (*b);
+}
+
+/**
+ * Get Bounds Rectangle of Bezier
+ */
+Rect Bezier::SolveBounds() const
+{
+ Rect result;
+ pair<Real, Real> tsols = BezierTurningPoints(x0, x1, x2, x3);
+
+ Real tp0; Real tp1; Real o;
+ Evaluate(tp0, o, tsols.first);
+ Evaluate(tp1, o, tsols.second);
+
+ Debug("x: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
+
+ vector<const Real*> v(4);
+ v[0] = &x0;
+ v[1] = &x3;
+ v[2] = &tp0;
+ v[3] = &tp1;
+
+ // Not using a lambda to keep this compiling on cabellera
+ sort(v.begin(), v.end(), CompRealByPtr);
+
+ result.x = *(v[0]);
+ result.w = *(v[3]) - result.x;
+
+ // Do the same thing for y component (wow this is a mess)
+ tsols = BezierTurningPoints(y0, y1, y2, y3);
+ Evaluate(o, tp0, tsols.first);
+ Evaluate(o, tp1, tsols.second);
+
+
+ Debug("y: tp0 is %f tp1 is %f", Float(tp0), Float(tp1));
+
+ v[0] = &y0;
+ v[1] = &y3;
+ v[2] = &tp0;
+ v[3] = &tp1;
+ sort(v.begin(), v.end(), CompRealByPtr);
+
+ result.y = *(v[0]);
+ result.h = *(v[3]) - result.y;
+
+ Debug("Solved Bezier %s bounds as %s", Str().c_str(), result.Str().c_str());
+ return result;
+}
+
+} // end namespace
extern int Factorial(int n);
extern int BinomialCoeff(int n, int k);
extern Real Bernstein(int k, int n, const Real & u);
+
+ inline std::pair<Real,Real> SolveQuadratic(const Real & a, const Real & b, const Real & c)
+ {
+ Real x0((b + Sqrt(b*b - Real(4)*a*c))/(Real(2)*a));
+ Real x1((b - Sqrt(b*b - Real(4)*a*c))/(Real(2)*a));
+ return std::pair<Real,Real>(x0,x1);
+ }
/** A _cubic_ bezier. **/
struct Bezier
Real x2; Real y2;
Real x3; Real y3;
Bezier() = default; // Needed so we can fread/fwrite this struct... for now.
- Bezier(Real _x0, Real _y0, Real _x1, Real _y1, Real _x2, Real _y2, Real _x3, Real _y3) : x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), x3(_x3), y3(_y3) {}
+ Bezier(Real _x0, Real _y0, Real _x1, Real _y1, Real _x2, Real _y2, Real _x3, Real _y3) : x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), x3(_x3), y3(_y3)
+ {
+
+ }
Bezier(Real _x0, Real _y0, Real _x1, Real _y1, Real _x2, Real _y2) : x0(_x0), y0(_y0), x1(_x1), y1(_y1), x2(_x2), y2(_y2), x3(_x2), y3(_y2) {}
s << "Bezier{" << Float(x0) << "," << Float(y0) << " -> " << Float(x1) << "," << Float(y1) << " -> " << Float(x2) << "," << Float(y2) << " -> " << Float(x3) << "," << Float(y3) << "}";
return s.str();
}
+
+ /**
+ * Construct absolute control points using relative control points to a bounding rectangle
+ * ie: If cpy is relative to bounds rectangle, this will be absolute
+ */
Bezier(const Bezier & cpy, const Rect & t = Rect(0,0,1,1)) : x0(cpy.x0), y0(cpy.y0), x1(cpy.x1), y1(cpy.y1), x2(cpy.x2),y2(cpy.y2), x3(cpy.x3), y3(cpy.y3)
{
x0 *= t.w;
y3 += t.y;
}
- Rect ToRect() {return Rect(x0,y0,x3-x0,y3-y0);}
+ Rect SolveBounds() const;
+
+ /** Convert absolute control points to control points relative to bounds
+ * (This basically does the opposite of the Copy constructor)
+ * ie: If this is absolute, the returned Bezier will be relative to the bounds rectangle
+ */
+ Bezier CopyInverse(const Rect & bounds) const
+ {
+ // x' <- (x - x0)/w etc
+ // special cases when w or h = 0
+ // (So can't just use the Copy constructor on the inverse of bounds)
+ // Rect inverse = {-bounds.x/bounds.w, -bounds.y/bounds.h, Real(1)/bounds.w, Real(1)/bounds.h};
+ Bezier result;
+ if (bounds.w == 0)
+ {
+ result.x0 = 0;
+ result.x1 = 0;
+ result.x2 = 0;
+ result.x3 = 0;
+ }
+ else
+ {
+ result.x0 = (x0 - bounds.x)/bounds.w;
+ result.x1 = (x1 - bounds.x)/bounds.w;
+ result.x2 = (x2 - bounds.x)/bounds.w;
+ result.x3 = (x3 - bounds.x)/bounds.w;
+ }
+
+ if (bounds.h == 0)
+ {
+ result.y0 = 0;
+ result.y1 = 0;
+ result.y2 = 0;
+ result.y3 = 0;
+ }
+ else
+ {
+ result.y0 = (y0 - bounds.y)/bounds.h;
+ result.y1 = (y1 - bounds.y)/bounds.h;
+ result.y2 = (y2 - bounds.y)/bounds.h;
+ result.y3 = (y3 - bounds.y)/bounds.h;
+ }
+ return result;
+ }
+
/** Evaluate the Bezier at parametric parameter u, puts resultant point in (x,y) **/
- void Evaluate(Real & x, Real & y, const Real & u)
+ void Evaluate(Real & x, Real & y, const Real & u) const
{
Real coeff[4];
for (unsigned i = 0; i < 4; ++i)
Debug("Bezier data...");
LoadStructVector<Bezier>(file, chunk_size/sizeof(Bezier), m_objects.beziers);
break;
+
+ case CT_OBJGROUPS:
+ Debug("Group data...");
+ Warn("Not handled because lazy");
+ break;
}
}
Debug("Successfully loaded %u objects from \"%s\"", ObjectCount(), filename.c_str());
#endif
}
-void Document::Add(ObjectType type, const Rect & bounds, unsigned data_index)
+unsigned Document::AddGroup(unsigned start_index, unsigned end_index)
+{
+ //TODO: Set bounds rect?
+ unsigned result = Add(GROUP, Rect(0,0,1,1),0);
+ m_objects.groups[m_count-1].first = start_index;
+ m_objects.groups[m_count-1].second = end_index;
+ return result;
+}
+
+unsigned Document::AddBezier(const Bezier & bezier)
+{
+ unsigned index = AddBezierData(bezier);
+ return Add(BEZIER, bezier.SolveBounds(), index);
+}
+
+unsigned Document::Add(ObjectType type, const Rect & bounds, unsigned data_index)
{
m_objects.types.push_back(type);
m_objects.bounds.push_back(bounds);
m_objects.data_indices.push_back(data_index);
- ++m_count; // Why can't we just use the size of types or something?
+ m_objects.groups.push_back(pair<unsigned, unsigned>(data_index, data_index));
+ return (m_count++); // Why can't we just use the size of types or something?
}
unsigned Document::AddBezierData(const Bezier & bezier)
{
string d = child.attribute("d").as_string();
Debug("Path data attribute is \"%s\"", d.c_str());
- ParseSVGPathData(d, transform);
+ pair<unsigned, unsigned> range = ParseSVGPathData(d, transform);
+ AddGroup(range.first, range.second);
+
}
else if (strcmp(child.name(), "line") == 0)
{
Real y1(child.attribute("y2").as_float());
TransformXYPair(x0,y0,transform);
TransformXYPair(x1,y1,transform);
- unsigned index = AddBezierData(Bezier(x0,y0,x1,y1,x1,y1,x1,y1));
- Add(BEZIER, Rect(0,0,1,1), index);
+ AddBezier(Bezier(x0,y0,x1,y1,x1,y1,x1,y1));
}
else if (strcmp(child.name(), "rect") == 0)
{
// Fear the wrath of the tokenizing svg data
// Seriously this isn't really very DOM-like at all is it?
-void Document::ParseSVGPathData(const string & d, const SVGMatrix & transform)
+pair<unsigned, unsigned> Document::ParseSVGPathData(const string & d, const SVGMatrix & transform)
{
Real x[4] = {0,0,0,0};
Real y[4] = {0,0,0,0};
bool start = false;
+
static string delims("()[],{}<>;:=LlHhVvmMqQzZcC");
+
+ pair<unsigned, unsigned> range(m_count, m_count);
while (i < d.size() && GetToken(d, token, i, delims).size() > 0)
{
for (int j = 0; j < 4; ++j)
TransformXYPair(x[j],y[j], transform);
- unsigned index = AddBezierData(Bezier(x[0],y[0],x[1],y[1],x[2],y[2],x[3],y[3]));
- Add(BEZIER,Rect(0,0,1,1),index);
-
+ range.second = AddBezier(Bezier(x[0],y[0],x[1],y[1],x[2],y[2],x[3],y[3]));
//Debug("[%u] curveto %f,%f %f,%f %f,%f", index, Float(x[1]),Float(y[1]),Float(x[2]),Float(y[2]),Float(x[3]),Float(y[3]));
TransformXYPair(x[1],y[1],transform);
- unsigned index = AddBezierData(Bezier(x[0],y[0],x[1],y[1],x[1],y[1],x[1],y[1]));
- Add(BEZIER,Rect(0,0,1,1),index);
+ range.second = AddBezier(Bezier(x[0],y[0],x[1],y[1],x[1],y[1],x[1],y[1]));
//Debug("[%u] lineto %f,%f %f,%f", index, Float(x[0]),Float(y[0]),Float(x[1]),Float(y[1]));
for (int j = 0; j < 4; ++j)
TransformXYPair(x[j],y[j], transform);
- unsigned index = AddBezierData(Bezier(x[0],y[0],x[1],y[1],x[2],y[2],x[3],y[3]));
- Add(BEZIER,Rect(0,0,1,1),index);
-
+ range.second = AddBezier(Bezier(x[0],y[0],x[1],y[1],x[2],y[2],x[3],y[3]));
//Debug("[%u] returnto %f,%f %f,%f", index, Float(x[0]),Float(y[0]),Float(x[1]),Float(y[1]));
x[0] = x3;
}
prev_i = i;
}
+ return range;
}
void Document::SetFont(const string & font_filename)
// - Endpoints are the same.
// - cubic1 = quad0+(2/3)*(quad1-quad0)
// - cubic2 = quad2+(2/3)*(quad1-quad2)
- bezier_index = AddBezierData(Bezier(old_x + x, old_y + y, old_x + Real(2)*(inst_cx-old_x)/Real(3) + x, old_y + Real(2)*(inst_cy-old_y)/Real(3) + y,
+ bezier_index = AddBezier(Bezier(old_x + x, old_y + y, old_x + Real(2)*(inst_cx-old_x)/Real(3) + x, old_y + Real(2)*(inst_cy-old_y)/Real(3) + y,
current_x + Real(2)*(inst_cx-current_x)/Real(3) + x, current_y + Real(2)*(inst_cy-current_y)/Real(3) + y, current_x + x, current_y + y));
- Add(BEZIER,Rect(0,0,1,1),bezier_index);
break;
}
}
bool operator==(const Document & equ) const;
bool operator!=(const Document & equ) const {return !(this->operator==(equ));}
- void Add(ObjectType type, const Rect & bounds, unsigned data_index = 0);
+ unsigned AddGroup(unsigned start_index, unsigned end_index);
+ unsigned AddBezier(const Bezier & bezier);
+ unsigned Add(ObjectType type, const Rect & bounds, unsigned data_index = 0);
unsigned AddBezierData(const Bezier & bezier);
/** Parse an SVG node or SVG-group node, adding children to the document **/
void ParseSVGNode(pugi::xml_node & root, SVGMatrix & transform);
/** Parse an SVG path with string **/
- void ParseSVGPathData(const std::string & d, const SVGMatrix & transform);
+ std::pair<unsigned, unsigned> ParseSVGPathData(const std::string & d, const SVGMatrix & transform);
/** Modify an SVG transformation matrix **/
static void ParseSVGTransform(const std::string & s, SVGMatrix & transform);
RECT_FILLED,
RECT_OUTLINE,
BEZIER,
+ GROUP,
NUMBER_OF_OBJECT_TYPES
} ObjectType;
CT_OBJTYPES,
CT_OBJBOUNDS,
CT_OBJINDICES,
- CT_OBJBEZIERS
- //CT_OBJGROUPS
+ CT_OBJBEZIERS,
+ CT_OBJGROUPS
};
Colour() = default;
Colour(float _r, float _g, float _b, float _a) : r(_r), g(_g), b(_b), a(_a) {}
};
-
- struct ObjectData
+
+ struct Group
{
- Colour colour;
-
+ Colour shading;
};
struct Objects
/** Used by BEZIER only **/
std::vector<Bezier> beziers; // bezier curves - look up by data_indices
+
+ std::vector<std::pair<unsigned, unsigned> > groups;
};
class View;
}
else
{
-/* doc.AddBezierData(Bezier(0,0,0,1,1,0));
- doc.AddBezierData(Bezier(0,0,1,0,0,1));
- doc.AddBezierData(Bezier(0,0,1,1,1,0));
- doc.AddBezierData(Bezier(0,1,1,0,0,1));*/
-
-
-
- doc.AddText("abcde", 0.5, Real(0), Real(1));
-
- for(int x = 0; x < 8; ++x)
- {
-
- for (int y = 0; y < 8; ++y)
- {
- //doc.Add(static_cast<IPDF::ObjectType>((x^y)%3), Rect(0.2+x-4.0,0.2+y-4.0,0.6,0.6));
- //doc.Add(BEZIER, Rect(0.2+x-4.0, 0.2+y-4.0, 0.6,0.6), (x^y)%3);
- }
- }
- /* doc.Add(BEZIER, Rect(0.1,0.1,0.8,0.8), 0);
- doc.Add(BEZIER, Rect(0.1,0.1,0.8,0.8), 1);
- doc.Add(BEZIER, Rect(0.1,0.1,0.8,0.8), 2);
- doc.Add(BEZIER, Rect(0.1,0.1,0.8,0.8), 3);*/
- //doc.Add(CIRCLE_FILLED, Rect(0.1,0.1,0.8,0.8), 0);
+ //doc.AddBezier(Bezier(0,0, 1,0.5, 0.5,1, 1,1));
+ doc.AddText("c",1,0,0);
}
Debug("Start!");
Rect bounds(b[0],b[1],b[2],b[3]);
m_shader_program.Use();
m_ibo.Bind();
- glDrawElements(GL_LINES, (last_index-first_index)*2, GL_UNSIGNED_INT, (GLvoid*)(2*first_index*sizeof(uint32_t)));
+ glDrawElements(GL_LINES, (last_index-first_index)*2, GL_UNSIGNED_INT, (GLvoid*)(first_index*sizeof(uint32_t)));
}
m_ibo.Invalidate();
m_ibo.SetUsage(GraphicsBuffer::BufferUsageStaticDraw);
m_ibo.SetType(GraphicsBuffer::BufferTypeIndex);
- m_ibo.SetName("m_ibo: ObjectRenderer GPU indices");
m_ibo.Resize(max_objects * 2 * sizeof(uint32_t));
// BufferBuilder is used to construct the ibo
m_buffer_builder = new BufferBuilder<uint32_t>(m_ibo.Map(false, true, true), m_ibo.GetSize()); // new matches delete in ObjectRenderer::FinaliseBuffers
Rect bounds(CPURenderBounds(objects.bounds[m_indexes[i]], view, target));
PixelBounds pix_bounds(bounds);
-
- Bezier control(objects.beziers[objects.data_indices[m_indexes[i]]], bounds);
+ Bezier control(objects.beziers[objects.data_indices[m_indexes[i]]],CPURenderBounds(Rect(0,0,1,1), view, target));
//Debug("%s -> %s via %s", objects.beziers[objects.data_indices[m_indexes[i]]].Str().c_str(), control.Str().c_str(), bounds.Str().c_str());
- // Draw a rectangle around the bezier for debugging the coord transforms
- //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target);
- //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target);
- //ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target);
- //ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target);
+ // Draw a rectangle around the bezier for debugging the bounds rectangle calculations
+ ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y, target, Colour(1,0,0,1));
+ ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y+pix_bounds.h, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(0,1,0,1));
+ ObjectRenderer::RenderLineOnCPU(pix_bounds.x, pix_bounds.y, pix_bounds.x, pix_bounds.y+pix_bounds.h, target, Colour(0,0,1,1));
+ ObjectRenderer::RenderLineOnCPU(pix_bounds.x+pix_bounds.w, pix_bounds.y, pix_bounds.x+pix_bounds.w, pix_bounds.y+pix_bounds.h, target, Colour(1,0,1,1));
// Draw lines between the control points for debugging
//ObjectRenderer::RenderLineOnCPU((int64_t)control.x0, (int64_t)control.y0, (int64_t)control.x1, (int64_t)control.y1,target);
{
m_bezier_coeffs.SetType(GraphicsBuffer::BufferTypeTexture);
m_bezier_coeffs.SetUsage(GraphicsBuffer::BufferUsageDynamicDraw);
- m_bezier_coeffs.SetName("m_bezier_coeffs: Bezier coefficients");
m_bezier_coeffs.Resize(objects.beziers.size()*sizeof(GPUBezierCoeffs));
BufferBuilder<GPUBezierCoeffs> builder(m_bezier_coeffs.Map(false, true, true), m_bezier_coeffs.GetSize());
- for (auto bez = objects.beziers.begin(); bez != objects.beziers.end(); ++bez)
+
+ for (unsigned i = 0; i < objects.types.size(); ++i)
{
+ if (objects.types[i] != BEZIER) continue;
+ Bezier bez = objects.beziers[objects.data_indices[i]].CopyInverse(objects.bounds[i]);
+
GPUBezierCoeffs coeffs = {
- Float(bez->x0), Float(bez->y0),
- Float(bez->x1), Float(bez->y1),
- Float(bez->x2), Float(bez->y2),
- Float(bez->x3), Float(bez->y3)
+ Float(bez.x0), Float(bez.y0),
+ Float(bez.x1), Float(bez.y1),
+ Float(bez.x2), Float(bez.y2),
+ Float(bez.x3), Float(bez.y3)
};
builder.Add(coeffs);
}
m_bezier_ids.SetType(GraphicsBuffer::BufferTypeTexture);
m_bezier_ids.SetUsage(GraphicsBuffer::BufferUsageDynamicDraw);
- m_bezier_ids.SetName("m_bezier_ids: object data_indices");
m_bezier_ids.Upload(objects.data_indices.size() * sizeof(uint32_t), &objects.data_indices[0]);
glGenTextures(1, &m_bezier_id_buffer_texture);
glUniform1i(m_shader_program.GetUniformLocation("bezier_buffer_texture"), 0);
glUniform1i(m_shader_program.GetUniformLocation("bezier_id_buffer_texture"), 1);
m_ibo.Bind();
- glDrawElements(GL_LINES, (last_index-first_index)*2, GL_UNSIGNED_INT, (GLvoid*)(2*first_index*sizeof(uint32_t)));
+ glDrawElements(GL_LINES, (last_index-first_index)*2, GL_UNSIGNED_INT, (GLvoid*)(first_index*sizeof(uint32_t)));
+}
+
+/**
+ * Render Group (shading)
+ */
+void GroupRenderer::RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target, unsigned first_obj_id, unsigned last_obj_id)
+{
+ for (unsigned i = 0; i < m_indexes.size(); ++i)
+ {
+ if (m_indexes[i] < first_obj_id) continue;
+ if (m_indexes[i] >= last_obj_id) continue;
+
+ //pair<unsigned, unsigned> range = objects.groups[m_indexes[i]];
+
+
+ }
}
/**
GLuint m_bezier_id_buffer_texture;
};
+
+ /** Renderer for filled circles **/
+ class GroupRenderer : public ObjectRenderer
+ {
+ public:
+ GroupRenderer() : ObjectRenderer(GROUP, "shaders/rect_vert.glsl", "shaders/rect_frag.glsl", "shaders/rect_outline_geom.glsl") {}
+ virtual ~GroupRenderer() {}
+ virtual void RenderUsingCPU(const Objects & objects, const View & view, const CPURenderTarget & target, unsigned first_obj_id, unsigned last_obj_id);
+ };
+
}
#endif //_OBJECT_RENDERER_H
#define _REAL_H
#include "common.h"
+#include <cmath>
#define REAL_SINGLE 0
inline double Double(float f) {return (double)f;}
inline double Double(double f) {return (double)f;}
inline double Double(long double f) {return (double)(f);}
+ inline double Sqrt(double f) {return sqrt(f);}
inline Real Power(const Real & a, int n)
{
return r;
}
+
+
}
#endif //_REAL_H
--- /dev/null
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
+"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+
+<svg xmlns="http://www.w3.org/2000/svg"
+ xmlns:xlink="http://www.w3.org/1999/xlink"
+ width="800" height="600"
+ transform="translate(400,300)"
+ >
+ <path transform="scale(400,300)"
+ d = "M 0,1 C 0.5522,1 1,0.5522 1,0 M 0,1 C -0.5522,1 -1,0.5522 -1,0 M 0,-1 C 0.5522,-1 1,-0.5522 1,0 M 0,-1 C -0.5522,-1 -1,-0.5522 -1,0"
+ stroke="red"
+ stroke-width="0.001"
+ fill="none"
+ />
+
+
+ </svg>
m_object_renderers[RECT_OUTLINE] = new RectOutlineRenderer();
m_object_renderers[CIRCLE_FILLED] = new CircleFilledRenderer();
m_object_renderers[BEZIER] = new BezierRenderer();
+ m_object_renderers[GROUP] = new GroupRenderer();
// To add rendering for a new type of object;
// 1. Add enum to ObjectType in ipdf.h