Welcome to mirror list, hosted at ThFree Co, Russian Federation.

git.blender.org/blender.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntonio Vazquez <blendergit@gmail.com>2021-03-24 17:14:43 +0300
committerAntonio Vazquez <blendergit@gmail.com>2021-03-24 17:28:58 +0300
commita8a92cd15a5251377474fbfdcf9ff0298a8457a9 (patch)
treee706390707e62180be921b2f2de03fcb8793303e
parentce359da5b3ac98c9f7cfd6593cc1769ec3b7247b (diff)
GPencil: New modules for Import and Export
This patch adds support to export and import grease pencil in several formats. Inlude: * Export SVG * Export PDF (always from camera view) * Import SVG The import and export only support solid colors and not gradients or textures. Requires libharu and pugixml. For importing SVG, the NanoSVG lib is used, but this does not require installation (just a .h file embedded in the project folder) Example of PDF export: https://youtu.be/BMm0KeMJsI4 Reviewed By: #grease_pencil, HooglyBoogly Maniphest Tasks: T83190, T79875, T83191, T83192 Differential Revision: https://developer.blender.org/D10482
-rw-r--r--release/scripts/startup/bl_ui/space_topbar.py9
-rw-r--r--source/blender/blenkernel/BKE_gpencil_geom.h16
-rw-r--r--source/blender/blenkernel/intern/gpencil_geom.c553
-rw-r--r--source/blender/editors/gpencil/gpencil_utils.c15
-rw-r--r--source/blender/editors/include/ED_gpencil.h1
-rw-r--r--source/blender/editors/io/CMakeLists.txt15
-rw-r--r--source/blender/editors/io/io_gpencil.h45
-rw-r--r--source/blender/editors/io/io_gpencil_export.c430
-rw-r--r--source/blender/editors/io/io_gpencil_import.c195
-rw-r--r--source/blender/editors/io/io_gpencil_utils.c64
-rw-r--r--source/blender/editors/io/io_ops.c11
-rw-r--r--source/blender/editors/space_file/filelist.c2
-rw-r--r--source/blender/io/CMakeLists.txt2
-rw-r--r--source/blender/io/gpencil/CMakeLists.txt99
-rw-r--r--source/blender/io/gpencil/gpencil_io.h92
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.cc386
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_base.h116
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_capi.cc202
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_base.h38
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc311
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_pdf.h67
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.cc464
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_export_svg.h89
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.cc85
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_base.h41
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.cc253
-rw-r--r--source/blender/io/gpencil/intern/gpencil_io_import_svg.h56
-rw-r--r--source/blender/io/gpencil/nanosvg/nanosvg.h3313
-rw-r--r--source/blender/python/intern/CMakeLists.txt8
-rw-r--r--source/blender/python/intern/bpy_app_build_options.c14
30 files changed, 6990 insertions, 2 deletions
diff --git a/release/scripts/startup/bl_ui/space_topbar.py b/release/scripts/startup/bl_ui/space_topbar.py
index 7219922c379..adab0b0c88a 100644
--- a/release/scripts/startup/bl_ui/space_topbar.py
+++ b/release/scripts/startup/bl_ui/space_topbar.py
@@ -469,6 +469,8 @@ class TOPBAR_MT_file_import(Menu):
if bpy.app.build_options.alembic:
self.layout.operator("wm.alembic_import", text="Alembic (.abc)")
+ self.layout.operator("wm.gpencil_import_svg", text="SVG as Grease Pencil")
+
class TOPBAR_MT_file_export(Menu):
bl_idname = "TOPBAR_MT_file_export"
@@ -485,6 +487,13 @@ class TOPBAR_MT_file_export(Menu):
self.layout.operator(
"wm.usd_export", text="Universal Scene Description (.usd, .usdc, .usda)")
+ # Pugixml lib dependency
+ if bpy.app.build_options.pugixml:
+ self.layout.operator("wm.gpencil_export_svg", text="Grease Pencil as SVG")
+ # Haru lib dependency
+ if bpy.app.build_options.haru:
+ self.layout.operator("wm.gpencil_export_pdf", text="Grease Pencil as PDF")
+
class TOPBAR_MT_file_external_data(Menu):
bl_label = "External Data"
diff --git a/source/blender/blenkernel/BKE_gpencil_geom.h b/source/blender/blenkernel/BKE_gpencil_geom.h
index 89a794f2df3..a9bd0a524c4 100644
--- a/source/blender/blenkernel/BKE_gpencil_geom.h
+++ b/source/blender/blenkernel/BKE_gpencil_geom.h
@@ -27,10 +27,10 @@
extern "C" {
#endif
-struct BoundBox;
struct Depsgraph;
struct Main;
struct Object;
+struct RegionView3D;
struct Scene;
struct bGPDcurve;
struct bGPDframe;
@@ -173,6 +173,20 @@ void BKE_gpencil_stroke_uniform_subdivide(struct bGPdata *gpd,
const uint32_t target_number,
const bool select);
+void BKE_gpencil_stroke_to_view_space(struct RegionView3D *rv3d,
+ struct bGPDstroke *gps,
+ const float diff_mat[4][4]);
+void BKE_gpencil_stroke_from_view_space(struct RegionView3D *rv3d,
+ struct bGPDstroke *gps,
+ const float diff_mat[4][4]);
+struct bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
+ struct bGPdata *gpd,
+ const struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ const int subdivisions,
+ const float diff_mat[4][4]);
+float BKE_gpencil_stroke_average_pressure_get(struct bGPDstroke *gps);
+bool BKE_gpencil_stroke_is_pressure_constant(struct bGPDstroke *gps);
#ifdef __cplusplus
}
#endif
diff --git a/source/blender/blenkernel/intern/gpencil_geom.c b/source/blender/blenkernel/intern/gpencil_geom.c
index 8c4882854d1..5d8dd99b3ae 100644
--- a/source/blender/blenkernel/intern/gpencil_geom.c
+++ b/source/blender/blenkernel/intern/gpencil_geom.c
@@ -46,9 +46,11 @@
#include "DNA_mesh_types.h"
#include "DNA_meshdata_types.h"
#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
#include "BLT_translation.h"
+#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_gpencil.h"
#include "BKE_gpencil_curve.h"
@@ -3460,4 +3462,555 @@ void BKE_gpencil_stroke_uniform_subdivide(bGPdata *gpd,
BKE_gpencil_stroke_geometry_update(gpd, gps);
}
+/**
+ * Stroke to view space
+ * Transforms a stroke to view space. This allows for manipulations in 2D but also easy conversion
+ * back to 3D.
+ * Note: also takes care of parent space transform
+ */
+void BKE_gpencil_stroke_to_view_space(RegionView3D *rv3d,
+ bGPDstroke *gps,
+ const float diff_mat[4][4])
+{
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ /* Point to parent space. */
+ mul_v3_m4v3(&pt->x, diff_mat, &pt->x);
+ /* point to view space */
+ mul_m4_v3(rv3d->viewmat, &pt->x);
+ }
+}
+
+/**
+ * Stroke from view space
+ * Transforms a stroke from view space back to world space. Inverse of
+ * BKE_gpencil_stroke_to_view_space
+ * Note: also takes care of parent space transform
+ */
+void BKE_gpencil_stroke_from_view_space(RegionView3D *rv3d,
+ bGPDstroke *gps,
+ const float diff_mat[4][4])
+{
+ float inverse_diff_mat[4][4];
+ invert_m4_m4(inverse_diff_mat, diff_mat);
+
+ for (int i = 0; i < gps->totpoints; i++) {
+ bGPDspoint *pt = &gps->points[i];
+ mul_v3_m4v3(&pt->x, rv3d->viewinv, &pt->x);
+ mul_m4_v3(inverse_diff_mat, &pt->x);
+ }
+}
+
+/* ----------------------------------------------------------------------------- */
+/* Stroke to perimeter */
+
+typedef struct tPerimeterPoint {
+ struct tPerimeterPoint *next, *prev;
+ float x, y, z;
+} tPerimeterPoint;
+
+static tPerimeterPoint *new_perimeter_point(const float pt[3])
+{
+ tPerimeterPoint *new_pt = MEM_callocN(sizeof(tPerimeterPoint), __func__);
+ copy_v3_v3(&new_pt->x, pt);
+ return new_pt;
+}
+
+static int generate_arc_from_point_to_point(ListBase *list,
+ tPerimeterPoint *from,
+ tPerimeterPoint *to,
+ float center_pt[3],
+ int subdivisions,
+ bool clockwise)
+{
+ float vec_from[2];
+ float vec_to[2];
+ sub_v2_v2v2(vec_from, &from->x, center_pt);
+ sub_v2_v2v2(vec_to, &to->x, center_pt);
+ if (is_zero_v2(vec_from) || is_zero_v2(vec_to)) {
+ return 0;
+ }
+
+ float dot = dot_v2v2(vec_from, vec_to);
+ float det = cross_v2v2(vec_from, vec_to);
+ float angle = clockwise ? M_PI - atan2f(-det, -dot) : atan2f(-det, -dot) + M_PI;
+
+ /* Number of points is 2^(n+1) + 1 on half a circle (n=subdivisions)
+ * so we multiply by (angle / pi) to get the right amount of
+ * points to insert. */
+ int num_points = (int)(((1 << (subdivisions + 1)) - 1) * (angle / M_PI));
+ if (num_points > 0) {
+ float angle_incr = angle / (float)num_points;
+
+ float vec_p[3];
+ float vec_t[3];
+ float tmp_angle;
+ tPerimeterPoint *last_point;
+ if (clockwise) {
+ last_point = to;
+ copy_v2_v2(vec_t, vec_to);
+ }
+ else {
+ last_point = from;
+ copy_v2_v2(vec_t, vec_from);
+ }
+
+ for (int i = 0; i < num_points - 1; i++) {
+ tmp_angle = (i + 1) * angle_incr;
+
+ rotate_v2_v2fl(vec_p, vec_t, tmp_angle);
+ add_v2_v2(vec_p, center_pt);
+ vec_p[2] = center_pt[2];
+
+ tPerimeterPoint *new_point = new_perimeter_point(vec_p);
+ if (clockwise) {
+ BLI_insertlinkbefore(list, last_point, new_point);
+ }
+ else {
+ BLI_insertlinkafter(list, last_point, new_point);
+ }
+
+ last_point = new_point;
+ }
+
+ return num_points - 1;
+ }
+
+ return 0;
+}
+
+static int generate_semi_circle_from_point_to_point(ListBase *list,
+ tPerimeterPoint *from,
+ tPerimeterPoint *to,
+ int subdivisions)
+{
+ int num_points = (1 << (subdivisions + 1)) + 1;
+ float center_pt[3];
+ interp_v3_v3v3(center_pt, &from->x, &to->x, 0.5f);
+
+ float vec_center[2];
+ sub_v2_v2v2(vec_center, &from->x, center_pt);
+ if (is_zero_v2(vec_center)) {
+ return 0;
+ }
+
+ float vec_p[3];
+ float angle_incr = M_PI / ((float)num_points - 1);
+
+ tPerimeterPoint *last_point = from;
+ for (int i = 1; i < num_points; i++) {
+ float angle = i * angle_incr;
+
+ /* Rotate vector around point to get perimeter points. */
+ rotate_v2_v2fl(vec_p, vec_center, angle);
+ add_v2_v2(vec_p, center_pt);
+ vec_p[2] = center_pt[2];
+
+ tPerimeterPoint *new_point = new_perimeter_point(vec_p);
+ BLI_insertlinkafter(list, last_point, new_point);
+
+ last_point = new_point;
+ }
+
+ return num_points - 1;
+}
+
+static int generate_perimeter_cap(const float point[4],
+ const float other_point[4],
+ float radius,
+ ListBase *list,
+ int subdivisions,
+ short cap_type)
+{
+ float cap_vec[2];
+ sub_v2_v2v2(cap_vec, other_point, point);
+ normalize_v2(cap_vec);
+
+ float cap_nvec[2];
+ if (is_zero_v2(cap_vec)) {
+ cap_nvec[0] = 0;
+ cap_nvec[1] = radius;
+ }
+ else {
+ cap_nvec[0] = -cap_vec[1];
+ cap_nvec[1] = cap_vec[0];
+ mul_v2_fl(cap_nvec, radius);
+ }
+ float cap_nvec_inv[2];
+ negate_v2_v2(cap_nvec_inv, cap_nvec);
+
+ float vec_perimeter[3];
+ copy_v3_v3(vec_perimeter, point);
+ add_v2_v2(vec_perimeter, cap_nvec);
+
+ float vec_perimeter_inv[3];
+ copy_v3_v3(vec_perimeter_inv, point);
+ add_v2_v2(vec_perimeter_inv, cap_nvec_inv);
+
+ tPerimeterPoint *p_pt = new_perimeter_point(vec_perimeter);
+ tPerimeterPoint *p_pt_inv = new_perimeter_point(vec_perimeter_inv);
+
+ BLI_addtail(list, p_pt);
+ BLI_addtail(list, p_pt_inv);
+
+ int num_points = 0;
+ if (cap_type == GP_STROKE_CAP_ROUND) {
+ num_points += generate_semi_circle_from_point_to_point(list, p_pt, p_pt_inv, subdivisions);
+ }
+
+ return num_points + 2;
+}
+
+/**
+ * Calculate the perimeter (outline) of a stroke as list of tPerimeterPoint.
+ * \param subdivisions: Number of subdivions for the start and end caps
+ * \return: list of tPerimeterPoint
+ */
+static ListBase *gpencil_stroke_perimeter_ex(const bGPdata *gpd,
+ const bGPDlayer *gpl,
+ const bGPDstroke *gps,
+ int subdivisions,
+ int *r_num_perimeter_points)
+{
+ /* sanity check */
+ if (gps->totpoints < 1) {
+ return NULL;
+ }
+
+ float defaultpixsize = 1000.0f / gpd->pixfactor;
+ float stroke_radius = ((gps->thickness + gpl->line_change) / defaultpixsize) / 2.0f;
+
+ ListBase *perimeter_right_side = MEM_callocN(sizeof(ListBase), __func__);
+ ListBase *perimeter_left_side = MEM_callocN(sizeof(ListBase), __func__);
+ int num_perimeter_points = 0;
+
+ bGPDspoint *first = &gps->points[0];
+ bGPDspoint *last = &gps->points[gps->totpoints - 1];
+
+ float first_radius = stroke_radius * first->pressure;
+ float last_radius = stroke_radius * last->pressure;
+
+ bGPDspoint *first_next;
+ bGPDspoint *last_prev;
+ if (gps->totpoints > 1) {
+ first_next = &gps->points[1];
+ last_prev = &gps->points[gps->totpoints - 2];
+ }
+ else {
+ first_next = first;
+ last_prev = last;
+ }
+
+ float first_pt[3];
+ float last_pt[3];
+ float first_next_pt[3];
+ float last_prev_pt[3];
+ copy_v3_v3(first_pt, &first->x);
+ copy_v3_v3(last_pt, &last->x);
+ copy_v3_v3(first_next_pt, &first_next->x);
+ copy_v3_v3(last_prev_pt, &last_prev->x);
+
+ /* edgecase if single point */
+ if (gps->totpoints == 1) {
+ first_next_pt[0] += 1.0f;
+ last_prev_pt[0] -= 1.0f;
+ }
+
+ /* generate points for start cap */
+ num_perimeter_points += generate_perimeter_cap(
+ first_pt, first_next_pt, first_radius, perimeter_right_side, subdivisions, gps->caps[0]);
+
+ /* generate perimeter points */
+ float curr_pt[3], next_pt[3], prev_pt[3];
+ float vec_next[2], vec_prev[2];
+ float nvec_next[2], nvec_prev[2];
+ float nvec_next_pt[3], nvec_prev_pt[3];
+ float vec_tangent[2];
+
+ float vec_miter_left[2], vec_miter_right[2];
+ float miter_left_pt[3], miter_right_pt[3];
+
+ for (int i = 1; i < gps->totpoints - 1; i++) {
+ bGPDspoint *curr = &gps->points[i];
+ bGPDspoint *prev = &gps->points[i - 1];
+ bGPDspoint *next = &gps->points[i + 1];
+ float radius = stroke_radius * curr->pressure;
+
+ copy_v3_v3(curr_pt, &curr->x);
+ copy_v3_v3(next_pt, &next->x);
+ copy_v3_v3(prev_pt, &prev->x);
+
+ sub_v2_v2v2(vec_prev, curr_pt, prev_pt);
+ sub_v2_v2v2(vec_next, next_pt, curr_pt);
+ float prev_length = len_v2(vec_prev);
+ float next_length = len_v2(vec_next);
+
+ if (normalize_v2(vec_prev) == 0.0f) {
+ vec_prev[0] = 1.0f;
+ vec_prev[1] = 0.0f;
+ }
+ if (normalize_v2(vec_next) == 0.0f) {
+ vec_next[0] = 1.0f;
+ vec_next[1] = 0.0f;
+ }
+
+ nvec_prev[0] = -vec_prev[1];
+ nvec_prev[1] = vec_prev[0];
+
+ nvec_next[0] = -vec_next[1];
+ nvec_next[1] = vec_next[0];
+
+ add_v2_v2v2(vec_tangent, vec_prev, vec_next);
+ if (normalize_v2(vec_tangent) == 0.0f) {
+ copy_v2_v2(vec_tangent, nvec_prev);
+ }
+
+ vec_miter_left[0] = -vec_tangent[1];
+ vec_miter_left[1] = vec_tangent[0];
+
+ /* calculate miter length */
+ float an1 = dot_v2v2(vec_miter_left, nvec_prev);
+ if (an1 == 0.0f) {
+ an1 = 1.0f;
+ }
+ float miter_length = radius / an1;
+ if (miter_length <= 0.0f) {
+ miter_length = 0.01f;
+ }
+
+ normalize_v2_length(vec_miter_left, miter_length);
+
+ copy_v2_v2(vec_miter_right, vec_miter_left);
+ negate_v2(vec_miter_right);
+
+ float angle = dot_v2v2(vec_next, nvec_prev);
+ /* add two points if angle is close to beeing straight */
+ if (fabsf(angle) < 0.0001f) {
+ normalize_v2_length(nvec_prev, radius);
+ normalize_v2_length(nvec_next, radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ negate_v2(nvec_next);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_left_side, normal_prev);
+ BLI_addtail(perimeter_right_side, normal_next);
+ num_perimeter_points += 2;
+ }
+ else {
+ /* bend to the left */
+ if (angle < 0.0f) {
+ normalize_v2_length(nvec_prev, radius);
+ normalize_v2_length(nvec_next, radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_left_side, normal_prev);
+ BLI_addtail(perimeter_left_side, normal_next);
+ num_perimeter_points += 2;
+
+ num_perimeter_points += generate_arc_from_point_to_point(
+ perimeter_left_side, normal_prev, normal_next, curr_pt, subdivisions, true);
+
+ if (miter_length < prev_length && miter_length < next_length) {
+ copy_v3_v3(miter_right_pt, curr_pt);
+ add_v2_v2(miter_right_pt, vec_miter_right);
+ }
+ else {
+ copy_v3_v3(miter_right_pt, curr_pt);
+ negate_v2(nvec_next);
+ add_v2_v2(miter_right_pt, nvec_next);
+ }
+
+ tPerimeterPoint *miter_right = new_perimeter_point(miter_right_pt);
+ BLI_addtail(perimeter_right_side, miter_right);
+ num_perimeter_points++;
+ }
+ /* bend to the right */
+ else {
+ normalize_v2_length(nvec_prev, -radius);
+ normalize_v2_length(nvec_next, -radius);
+
+ copy_v3_v3(nvec_prev_pt, curr_pt);
+ add_v2_v2(nvec_prev_pt, nvec_prev);
+
+ copy_v3_v3(nvec_next_pt, curr_pt);
+ add_v2_v2(nvec_next_pt, nvec_next);
+
+ tPerimeterPoint *normal_prev = new_perimeter_point(nvec_prev_pt);
+ tPerimeterPoint *normal_next = new_perimeter_point(nvec_next_pt);
+
+ BLI_addtail(perimeter_right_side, normal_prev);
+ BLI_addtail(perimeter_right_side, normal_next);
+ num_perimeter_points += 2;
+
+ num_perimeter_points += generate_arc_from_point_to_point(
+ perimeter_right_side, normal_prev, normal_next, curr_pt, subdivisions, false);
+
+ if (miter_length < prev_length && miter_length < next_length) {
+ copy_v3_v3(miter_left_pt, curr_pt);
+ add_v2_v2(miter_left_pt, vec_miter_left);
+ }
+ else {
+ copy_v3_v3(miter_left_pt, curr_pt);
+ negate_v2(nvec_prev);
+ add_v2_v2(miter_left_pt, nvec_prev);
+ }
+
+ tPerimeterPoint *miter_left = new_perimeter_point(miter_left_pt);
+ BLI_addtail(perimeter_left_side, miter_left);
+ num_perimeter_points++;
+ }
+ }
+ }
+
+ /* generate points for end cap */
+ num_perimeter_points += generate_perimeter_cap(
+ last_pt, last_prev_pt, last_radius, perimeter_right_side, subdivisions, gps->caps[1]);
+
+ /* merge both sides to one list */
+ BLI_listbase_reverse(perimeter_right_side);
+ BLI_movelisttolist(perimeter_left_side,
+ perimeter_right_side); // perimeter_left_side contains entire list
+ ListBase *perimeter_list = perimeter_left_side;
+
+ /* close by creating a point close to the first (make a small gap) */
+ float close_pt[3];
+ tPerimeterPoint *close_first = (tPerimeterPoint *)perimeter_list->first;
+ tPerimeterPoint *close_last = (tPerimeterPoint *)perimeter_list->last;
+ interp_v3_v3v3(close_pt, &close_last->x, &close_first->x, 0.99f);
+
+ if (compare_v3v3(close_pt, &close_first->x, FLT_EPSILON) == false) {
+ tPerimeterPoint *close_p_pt = new_perimeter_point(close_pt);
+ BLI_addtail(perimeter_list, close_p_pt);
+ num_perimeter_points++;
+ }
+
+ /* free temp data */
+ BLI_freelistN(perimeter_right_side);
+ MEM_freeN(perimeter_right_side);
+
+ *r_num_perimeter_points = num_perimeter_points;
+ return perimeter_list;
+}
+
+/**
+ * Calculates the perimeter of a stroke projected from the view and
+ * returns it as a new stroke.
+ * \param subdivisions: Number of subdivions for the start and end caps
+ * \return: bGPDstroke pointer to stroke perimeter
+ */
+bGPDstroke *BKE_gpencil_stroke_perimeter_from_view(struct RegionView3D *rv3d,
+ bGPdata *gpd,
+ const bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const int subdivisions,
+ const float diff_mat[4][4])
+{
+ if (gps->totpoints == 0) {
+ return NULL;
+ }
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, true, false);
+ const bool cyclic = ((gps_temp->flag & GP_STROKE_CYCLIC) != 0);
+
+ /* If Cyclic, add a new point. */
+ if (cyclic && (gps_temp->totpoints > 1)) {
+ gps_temp->totpoints++;
+ gps_temp->points = MEM_recallocN(gps_temp->points,
+ sizeof(*gps_temp->points) * gps_temp->totpoints);
+ bGPDspoint *pt_src = &gps_temp->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[gps_temp->totpoints - 1];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = pt_src->pressure;
+ pt_dst->strength = pt_src->strength;
+ pt_dst->uv_fac = 1.0f;
+ pt_dst->uv_rot = 0;
+ }
+
+ BKE_gpencil_stroke_to_view_space(rv3d, gps_temp, diff_mat);
+ int num_perimeter_points = 0;
+ ListBase *perimeter_points = gpencil_stroke_perimeter_ex(
+ gpd, gpl, gps_temp, subdivisions, &num_perimeter_points);
+
+ if (num_perimeter_points == 0) {
+ return NULL;
+ }
+
+ /* Create new stroke. */
+ bGPDstroke *perimeter_stroke = BKE_gpencil_stroke_new(gps_temp->mat_nr, num_perimeter_points, 1);
+
+ int i = 0;
+ LISTBASE_FOREACH_INDEX (tPerimeterPoint *, curr, perimeter_points, i) {
+ bGPDspoint *pt = &perimeter_stroke->points[i];
+
+ copy_v3_v3(&pt->x, &curr->x);
+ pt->pressure = 0.0f;
+ pt->strength = 1.0f;
+
+ pt->flag |= GP_SPOINT_SELECT;
+ }
+
+ BKE_gpencil_stroke_from_view_space(rv3d, perimeter_stroke, diff_mat);
+
+ /* Free temp data. */
+ BLI_freelistN(perimeter_points);
+ MEM_freeN(perimeter_points);
+
+ /* Triangles cache needs to be recalculated. */
+ BKE_gpencil_stroke_geometry_update(gpd, perimeter_stroke);
+
+ perimeter_stroke->flag |= GP_STROKE_SELECT | GP_STROKE_CYCLIC;
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ return perimeter_stroke;
+}
+
+/** Get average pressure. */
+float BKE_gpencil_stroke_average_pressure_get(bGPDstroke *gps)
+{
+
+ if (gps->totpoints == 1) {
+ return gps->points[0].pressure;
+ }
+
+ float tot = 0.0f;
+ for (int i = 0; i < gps->totpoints; i++) {
+ const bGPDspoint *pt = &gps->points[i];
+ tot += pt->pressure;
+ }
+
+ return tot / (float)gps->totpoints;
+}
+
+/** Check if the thickness of the stroke is constant. */
+bool BKE_gpencil_stroke_is_pressure_constant(bGPDstroke *gps)
+{
+ if (gps->totpoints == 1) {
+ return true;
+ }
+
+ const float first_pressure = gps->points[0].pressure;
+ for (int i = 0; i < gps->totpoints; i++) {
+ const bGPDspoint *pt = &gps->points[i];
+ if (pt->pressure != first_pressure) {
+ return false;
+ }
+ }
+
+ return true;
+}
/** \} */
diff --git a/source/blender/editors/gpencil/gpencil_utils.c b/source/blender/editors/gpencil/gpencil_utils.c
index 5c041134a74..574670de7ca 100644
--- a/source/blender/editors/gpencil/gpencil_utils.c
+++ b/source/blender/editors/gpencil/gpencil_utils.c
@@ -596,6 +596,21 @@ bool ED_gpencil_stroke_material_editable(Object *ob, const bGPDlayer *gpl, const
return true;
}
+/* Check whether given stroke is visible for the current material. */
+bool ED_gpencil_stroke_material_visible(Object *ob, const bGPDstroke *gps)
+{
+ /* check if the color is editable */
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+
+ if (gp_style != NULL) {
+ if (gp_style->flag & GP_MATERIAL_HIDE) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
/* ******************************************************** */
/* Space Conversion */
diff --git a/source/blender/editors/include/ED_gpencil.h b/source/blender/editors/include/ED_gpencil.h
index f3b5abb1072..e9ac21f60cf 100644
--- a/source/blender/editors/include/ED_gpencil.h
+++ b/source/blender/editors/include/ED_gpencil.h
@@ -150,6 +150,7 @@ bool ED_gpencil_stroke_can_use(const struct bContext *C, const struct bGPDstroke
bool ED_gpencil_stroke_material_editable(struct Object *ob,
const struct bGPDlayer *gpl,
const struct bGPDstroke *gps);
+bool ED_gpencil_stroke_material_visible(struct Object *ob, const struct bGPDstroke *gps);
/* ----------- Grease Pencil Operators ----------------- */
diff --git a/source/blender/editors/io/CMakeLists.txt b/source/blender/editors/io/CMakeLists.txt
index e7effd05d34..d45c7ca9b75 100644
--- a/source/blender/editors/io/CMakeLists.txt
+++ b/source/blender/editors/io/CMakeLists.txt
@@ -24,6 +24,7 @@ set(INC
../../depsgraph
../../io/alembic
../../io/collada
+ ../../io/gpencil
../../io/usd
../../makesdna
../../makesrna
@@ -39,12 +40,16 @@ set(SRC
io_alembic.c
io_cache.c
io_collada.c
+ io_gpencil_import.c
+ io_gpencil_export.c
+ io_gpencil_utils.c
io_ops.c
io_usd.c
io_alembic.h
io_cache.h
io_collada.h
+ io_gpencil.h
io_ops.h
io_usd.h
)
@@ -79,4 +84,14 @@ if(WITH_INTERNATIONAL)
add_definitions(-DWITH_INTERNATIONAL)
endif()
+if(WITH_PUGIXML)
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ add_definitions(-DWITH_HARU)
+endif()
+
+list(APPEND LIB bf_gpencil)
+
blender_add_lib(bf_editor_io "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/editors/io/io_gpencil.h b/source/blender/editors/io/io_gpencil.h
new file mode 100644
index 00000000000..98cb8b13310
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil.h
@@ -0,0 +1,45 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+#ifndef __IO_GPENCIL_H__
+#define __IO_GPENCIL_H__
+
+/** \file
+ * \ingroup editor/io
+ */
+
+struct ARegion;
+struct bContext;
+struct View3D;
+struct wmOperator;
+struct wmOperatorType;
+
+void WM_OT_gpencil_import_svg(struct wmOperatorType *ot);
+
+#ifdef WITH_PUGIXML
+void WM_OT_gpencil_export_svg(struct wmOperatorType *ot);
+#endif
+#ifdef WITH_HARU
+void WM_OT_gpencil_export_pdf(struct wmOperatorType *ot);
+#endif
+
+struct ARegion *get_invoke_region(struct bContext *C);
+struct View3D *get_invoke_view3d(struct bContext *C);
+
+#endif /* __IO_GPENCIL_H__ */
diff --git a/source/blender/editors/io/io_gpencil_export.c b/source/blender/editors/io/io_gpencil_export.c
new file mode 100644
index 00000000000..6f1e503403b
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_export.c
@@ -0,0 +1,430 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "BLI_path_util.h"
+#include "BLI_string.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_main.h"
+#include "BKE_report.h"
+#include "BKE_screen.h"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "io_gpencil.h"
+
+#include "gpencil_io.h"
+
+/* Definition of enum elements to export. */
+/* Common props for exporting. */
+static void gpencil_export_common_props_definition(wmOperatorType *ot)
+{
+ static const EnumPropertyItem select_items[] = {
+ {GP_EXPORT_ACTIVE, "ACTIVE", 0, "Active", "Include only the active object"},
+ {GP_EXPORT_SELECTED, "SELECTED", 0, "Selected", "Include selected objects"},
+ {GP_EXPORT_VISIBLE, "VISIBLE", 0, "Visible", "Include all visible objects"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ RNA_def_boolean(ot->srna, "use_fill", true, "Fill", "Export strokes with fill enabled");
+ RNA_def_enum(ot->srna,
+ "selected_object_type",
+ select_items,
+ GP_EXPORT_SELECTED,
+ "Object",
+ "Which objects to include in the export");
+ RNA_def_float(ot->srna,
+ "stroke_sample",
+ 0.0f,
+ 0.0f,
+ 100.0f,
+ "Sampling",
+ "Precision of stroke sampling. Low values mean a more precise result, and zero "
+ "disables sampling",
+ 0.0f,
+ 100.0f);
+ RNA_def_boolean(ot->srna,
+ "use_normalized_thickness",
+ false,
+ "Normalize",
+ "Export strokes with constant thickness");
+}
+
+static void set_export_filepath(bContext *C, wmOperator *op)
+{
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ Main *bmain = CTX_data_main(C);
+ char filepath[FILE_MAX];
+
+ if (BKE_main_blendfile_path(bmain)[0] == '\0') {
+ BLI_strncpy(filepath, "untitled", sizeof(filepath));
+ }
+ else {
+ BLI_strncpy(filepath, BKE_main_blendfile_path(bmain), sizeof(filepath));
+ }
+
+ BLI_path_extension_replace(filepath, sizeof(filepath), ".pdf");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ }
+}
+
+/* <-------- SVG single frame export. --------> */
+#ifdef WITH_PUGIXML
+static bool wm_gpencil_export_svg_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".svg")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_export_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ set_export_filepath(C, op);
+
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_export_svg_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
+ const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
+ const eGpencilExportSelect select_mode = RNA_enum_get(op->ptr, "selected_object_type");
+
+ const bool use_clip_camera = RNA_boolean_get(op->ptr, "use_clip_camera");
+
+ /* Set flags. */
+ int flag = 0;
+ SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
+ SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
+ SET_FLAG_FROM_TEST(flag, use_clip_camera, GP_EXPORT_CLIP_CAMERA);
+
+ GpencilIOParams params = {.C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = ob,
+ .mode = GP_EXPORT_TO_SVG,
+ .frame_start = CFRA,
+ .frame_end = CFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = 1.0f,
+ .select_mode = select_mode,
+ .frame_mode = GP_EXPORT_FRAME_ACTIVE,
+ .stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
+ .resolution = 1.0f};
+
+ /* Do export. */
+ WM_cursor_wait(true);
+ const bool done = gpencil_io_export(filename, &params);
+ WM_cursor_wait(false);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to export SVG");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_export_svg_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayout *box, *row;
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+
+ box = uiLayoutBox(layout);
+
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
+
+ row = uiLayoutRow(box, false);
+ uiItemR(row, imfptr, "selected_object_type", 0, NULL, ICON_NONE);
+
+ box = uiLayoutBox(layout);
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Export Options"), ICON_NONE);
+
+ uiLayout *col = uiLayoutColumn(box, false);
+ uiItemR(col, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_fill", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "use_clip_camera", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_export_svg_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_export_svg_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_export_svg_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_export_svg(wmOperatorType *ot)
+{
+ ot->name = "Export to SVG";
+ ot->description = "Export grease pencil to SVG";
+ ot->idname = "WM_OT_gpencil_export_svg";
+
+ ot->invoke = wm_gpencil_export_svg_invoke;
+ ot->exec = wm_gpencil_export_svg_exec;
+ ot->poll = wm_gpencil_export_svg_poll;
+ ot->ui = wm_gpencil_export_svg_draw;
+ ot->check = wm_gpencil_export_svg_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_ALPHA);
+
+ gpencil_export_common_props_definition(ot);
+
+ RNA_def_boolean(ot->srna,
+ "use_clip_camera",
+ false,
+ "Clip Camera",
+ "Clip drawings to camera size when export in camera view");
+}
+#endif
+
+/* <-------- PDF single frame export. --------> */
+#ifdef WITH_HARU
+static bool wm_gpencil_export_pdf_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".pdf")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".pdf");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_export_pdf_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ set_export_filepath(C, op);
+
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_export_pdf_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+ Object *ob = CTX_data_active_object(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ const bool use_fill = RNA_boolean_get(op->ptr, "use_fill");
+ const bool use_norm_thickness = RNA_boolean_get(op->ptr, "use_normalized_thickness");
+ const short select_mode = RNA_enum_get(op->ptr, "selected_object_type");
+ const short frame_mode = RNA_enum_get(op->ptr, "frame_mode");
+
+ /* Set flags. */
+ int flag = 0;
+ SET_FLAG_FROM_TEST(flag, use_fill, GP_EXPORT_FILL);
+ SET_FLAG_FROM_TEST(flag, use_norm_thickness, GP_EXPORT_NORM_THICKNESS);
+
+ GpencilIOParams params = {.C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = ob,
+ .mode = GP_EXPORT_TO_PDF,
+ .frame_start = SFRA,
+ .frame_end = EFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = 1.0f,
+ .select_mode = select_mode,
+ .frame_mode = frame_mode,
+ .stroke_sample = RNA_float_get(op->ptr, "stroke_sample"),
+ .resolution = 1.0f};
+
+ /* Do export. */
+ WM_cursor_wait(true);
+ const bool done = gpencil_io_export(filename, &params);
+ WM_cursor_wait(false);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to export PDF");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_export_pdf_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayout *box, *row, *col, *sub;
+
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+
+ box = uiLayoutBox(layout);
+
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Scene Options"), ICON_NONE);
+
+ row = uiLayoutRow(box, false);
+ uiItemR(row, imfptr, "selected_object_type", 0, NULL, ICON_NONE);
+
+ box = uiLayoutBox(layout);
+ row = uiLayoutRow(box, false);
+ uiItemL(row, IFACE_("Export Options"), ICON_NONE);
+
+ col = uiLayoutColumn(box, false);
+ sub = uiLayoutColumn(col, true);
+ uiItemR(sub, imfptr, "frame_mode", 0, IFACE_("Frame"), ICON_NONE);
+
+ uiLayoutSetPropSep(box, true);
+
+ sub = uiLayoutColumn(col, true);
+ uiItemR(sub, imfptr, "stroke_sample", 0, NULL, ICON_NONE);
+ uiItemR(sub, imfptr, "use_fill", 0, NULL, ICON_NONE);
+ uiItemR(sub, imfptr, "use_normalized_thickness", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_export_pdf_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_export_pdf_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_export_pdf_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_export_pdf(wmOperatorType *ot)
+{
+ ot->name = "Export to PDF";
+ ot->description = "Export grease pencil to PDF";
+ ot->idname = "WM_OT_gpencil_export_pdf";
+
+ ot->invoke = wm_gpencil_export_pdf_invoke;
+ ot->exec = wm_gpencil_export_pdf_exec;
+ ot->poll = wm_gpencil_export_pdf_poll;
+ ot->ui = wm_gpencil_export_pdf_draw;
+ ot->check = wm_gpencil_export_pdf_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_SAVE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_ALPHA);
+
+ static const EnumPropertyItem gpencil_export_frame_items[] = {
+ {GP_EXPORT_FRAME_ACTIVE, "ACTIVE", 0, "Active", "Include only active frame"},
+ {GP_EXPORT_FRAME_SELECTED, "SELECTED", 0, "Selected", "Include selected frames"},
+ {0, NULL, 0, NULL, NULL},
+ };
+
+ gpencil_export_common_props_definition(ot);
+ ot->prop = RNA_def_enum(ot->srna,
+ "frame_mode",
+ gpencil_export_frame_items,
+ GP_EXPORT_ACTIVE,
+ "Frames",
+ "Which frames to include in the export");
+}
+#endif
diff --git a/source/blender/editors/io/io_gpencil_import.c b/source/blender/editors/io/io_gpencil_import.c
new file mode 100644
index 00000000000..9768da85940
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_import.c
@@ -0,0 +1,195 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "BLI_path_util.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_report.h"
+
+#include "BLT_translation.h"
+
+#include "RNA_access.h"
+#include "RNA_define.h"
+
+#include "UI_interface.h"
+#include "UI_resources.h"
+
+#include "WM_api.h"
+#include "WM_types.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+
+#include "io_gpencil.h"
+
+#include "gpencil_io.h"
+
+/* <-------- SVG single frame import. --------> */
+static bool wm_gpencil_import_svg_common_check(bContext *UNUSED(C), wmOperator *op)
+{
+
+ char filepath[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filepath);
+
+ if (!BLI_path_extension_check(filepath, ".svg")) {
+ BLI_path_extension_ensure(filepath, FILE_MAX, ".svg");
+ RNA_string_set(op->ptr, "filepath", filepath);
+ return true;
+ }
+
+ return false;
+}
+
+static int wm_gpencil_import_svg_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
+{
+ WM_event_add_fileselect(C, op);
+
+ return OPERATOR_RUNNING_MODAL;
+}
+
+static int wm_gpencil_import_svg_exec(bContext *C, wmOperator *op)
+{
+ Scene *scene = CTX_data_scene(C);
+
+ if (!RNA_struct_property_is_set(op->ptr, "filepath")) {
+ BKE_report(op->reports, RPT_ERROR, "No filename given");
+ return OPERATOR_CANCELLED;
+ }
+
+ ARegion *region = get_invoke_region(C);
+ if (region == NULL) {
+ BKE_report(op->reports, RPT_ERROR, "Unable to find valid 3D View area");
+ return OPERATOR_CANCELLED;
+ }
+ View3D *v3d = get_invoke_view3d(C);
+
+ char filename[FILE_MAX];
+ RNA_string_get(op->ptr, "filepath", filename);
+
+ /* Set flags. */
+ int flag = 0;
+
+ const int resolution = RNA_int_get(op->ptr, "resolution");
+ const float scale = RNA_float_get(op->ptr, "scale");
+
+ GpencilIOParams params = {
+ .C = C,
+ .region = region,
+ .v3d = v3d,
+ .ob = NULL,
+ .mode = GP_IMPORT_FROM_SVG,
+ .frame_start = CFRA,
+ .frame_end = CFRA,
+ .frame_cur = CFRA,
+ .flag = flag,
+ .scale = scale,
+ .select_mode = 0,
+ .frame_mode = 0,
+ .stroke_sample = 0.0f,
+ .resolution = resolution,
+ };
+
+ /* Do Import. */
+ WM_cursor_wait(1);
+ const bool done = gpencil_io_import(filename, &params);
+ WM_cursor_wait(0);
+
+ if (!done) {
+ BKE_report(op->reports, RPT_WARNING, "Unable to import SVG");
+ }
+
+ return OPERATOR_FINISHED;
+}
+
+static void ui_gpencil_import_svg_settings(uiLayout *layout, PointerRNA *imfptr)
+{
+ uiLayoutSetPropSep(layout, true);
+ uiLayoutSetPropDecorate(layout, false);
+ uiLayout *col = uiLayoutColumn(layout, false);
+ uiItemR(col, imfptr, "resolution", 0, NULL, ICON_NONE);
+ uiItemR(col, imfptr, "scale", 0, NULL, ICON_NONE);
+}
+
+static void wm_gpencil_import_svg_draw(bContext *UNUSED(C), wmOperator *op)
+{
+ PointerRNA ptr;
+ RNA_pointer_create(NULL, op->type->srna, op->properties, &ptr);
+
+ ui_gpencil_import_svg_settings(op->layout, &ptr);
+}
+
+static bool wm_gpencil_import_svg_poll(bContext *C)
+{
+ if ((CTX_wm_window(C) == NULL) || (CTX_data_mode_enum(C) != CTX_MODE_OBJECT)) {
+ return false;
+ }
+
+ return true;
+}
+
+void WM_OT_gpencil_import_svg(wmOperatorType *ot)
+{
+ ot->name = "Import SVG";
+ ot->description = "Import SVG into grease pencil";
+ ot->idname = "WM_OT_gpencil_import_svg";
+
+ ot->invoke = wm_gpencil_import_svg_invoke;
+ ot->exec = wm_gpencil_import_svg_exec;
+ ot->poll = wm_gpencil_import_svg_poll;
+ ot->ui = wm_gpencil_import_svg_draw;
+ ot->check = wm_gpencil_import_svg_common_check;
+
+ WM_operator_properties_filesel(ot,
+ FILE_TYPE_OBJECT_IO,
+ FILE_BLENDER,
+ FILE_OPENFILE,
+ WM_FILESEL_FILEPATH | WM_FILESEL_RELPATH | WM_FILESEL_SHOW_PROPS,
+ FILE_DEFAULTDISPLAY,
+ FILE_SORT_DEFAULT);
+
+ RNA_def_int(ot->srna,
+ "resolution",
+ 10,
+ 1,
+ 30,
+ "Resolution",
+ "Resolution of the generated strokes",
+ 1,
+ 20);
+
+ RNA_def_float(ot->srna,
+ "scale",
+ 10.0f,
+ 0.001f,
+ 100.0f,
+ "Scale",
+ "Scale of the final strokes",
+ 0.001f,
+ 100.0f);
+}
diff --git a/source/blender/editors/io/io_gpencil_utils.c b/source/blender/editors/io/io_gpencil_utils.c
new file mode 100644
index 00000000000..259a669519a
--- /dev/null
+++ b/source/blender/editors/io/io_gpencil_utils.c
@@ -0,0 +1,64 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation.
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup editor/io
+ */
+
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_screen.h"
+
+#include "WM_api.h"
+
+#include "io_gpencil.h"
+
+ARegion *get_invoke_region(bContext *C)
+{
+ bScreen *screen = CTX_wm_screen(C);
+ if (screen == NULL) {
+ return NULL;
+ }
+ ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
+ if (area == NULL) {
+ return NULL;
+ }
+
+ ARegion *region = BKE_area_find_region_type(area, RGN_TYPE_WINDOW);
+
+ return region;
+}
+
+View3D *get_invoke_view3d(bContext *C)
+{
+ bScreen *screen = CTX_wm_screen(C);
+ if (screen == NULL) {
+ return NULL;
+ }
+ ScrArea *area = BKE_screen_find_big_area(screen, SPACE_VIEW3D, 0);
+ if (area == NULL) {
+ return NULL;
+ }
+ if (area) {
+ return area->spacedata.first;
+ }
+
+ return NULL;
+}
diff --git a/source/blender/editors/io/io_ops.c b/source/blender/editors/io/io_ops.c
index acb511a414d..9fa34a1c55d 100644
--- a/source/blender/editors/io/io_ops.c
+++ b/source/blender/editors/io/io_ops.c
@@ -38,6 +38,7 @@
#endif
#include "io_cache.h"
+#include "io_gpencil.h"
void ED_operatortypes_io(void)
{
@@ -54,6 +55,16 @@ void ED_operatortypes_io(void)
WM_operatortype_append(WM_OT_usd_export);
#endif
+ WM_operatortype_append(WM_OT_gpencil_import_svg);
+
+#ifdef WITH_PUGIXML
+ WM_operatortype_append(WM_OT_gpencil_export_svg);
+#endif
+
+#ifdef WITH_HARU
+ WM_operatortype_append(WM_OT_gpencil_export_pdf);
+#endif
+
WM_operatortype_append(CACHEFILE_OT_open);
WM_operatortype_append(CACHEFILE_OT_reload);
}
diff --git a/source/blender/editors/space_file/filelist.c b/source/blender/editors/space_file/filelist.c
index f5ec9a0e8a1..4c9f80bfa64 100644
--- a/source/blender/editors/space_file/filelist.c
+++ b/source/blender/editors/space_file/filelist.c
@@ -2543,7 +2543,7 @@ int ED_path_extension_type(const char *path)
if (BLI_path_extension_check(path, ".zip")) {
return FILE_TYPE_ARCHIVE;
}
- if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", NULL)) {
+ if (BLI_path_extension_check_n(path, ".obj", ".3ds", ".fbx", ".glb", ".gltf", ".svg", NULL)) {
return FILE_TYPE_OBJECT_IO;
}
if (BLI_path_extension_check_array(path, imb_ext_image)) {
diff --git a/source/blender/io/CMakeLists.txt b/source/blender/io/CMakeLists.txt
index 360cacc4360..f11ad7627b9 100644
--- a/source/blender/io/CMakeLists.txt
+++ b/source/blender/io/CMakeLists.txt
@@ -35,3 +35,5 @@ endif()
if(WITH_USD)
add_subdirectory(usd)
endif()
+
+add_subdirectory(gpencil)
diff --git a/source/blender/io/gpencil/CMakeLists.txt b/source/blender/io/gpencil/CMakeLists.txt
new file mode 100644
index 00000000000..f394075cb9a
--- /dev/null
+++ b/source/blender/io/gpencil/CMakeLists.txt
@@ -0,0 +1,99 @@
+# ***** BEGIN GPL LICENSE BLOCK *****
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; either version 2
+# of the License, or (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software Foundation,
+# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+#
+# The Original Code is Copyright (C) 2006, Blender Foundation
+# All rights reserved.
+# ***** END GPL LICENSE BLOCK *****
+
+set(INC
+ .
+ ../common
+ ../../blenkernel
+ ../../blenlib
+ ../../blenloader
+ ../../bmesh
+ ../../depsgraph
+ ../../editors/include
+ ../../makesdna
+ ../../makesrna
+ ../../windowmanager
+ ../../../../intern/clog
+ ../../../../intern/guardedalloc
+ ../../../../intern/utfconv
+)
+
+set(INC_SYS
+)
+
+set(SRC
+ intern/gpencil_io_capi.cc
+
+ # This line must be removed if NanoSVG is moved to extern
+ nanosvg/nanosvg.h
+
+ gpencil_io.h
+
+ intern/gpencil_io_base.h
+ intern/gpencil_io_base.cc
+
+ intern/gpencil_io_import_base.h
+ intern/gpencil_io_import_svg.h
+ intern/gpencil_io_import_base.cc
+ intern/gpencil_io_import_svg.cc
+
+ intern/gpencil_io_export_base.h
+)
+
+set(LIB
+ bf_blenkernel
+ bf_blenlib
+ bf_io_common
+)
+
+if(WITH_PUGIXML)
+ list(APPEND SRC
+ intern/gpencil_io_export_svg.h
+ intern/gpencil_io_export_svg.cc
+ )
+ list(APPEND INC
+ ${PUGIXML_INCLUDE_DIR}
+ )
+ list(APPEND LIB
+ ${PUGIXML_LIBRARIES}
+ )
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ list(APPEND SRC
+ intern/gpencil_io_export_pdf.h
+ intern/gpencil_io_export_pdf.cc
+ )
+ list(APPEND INC
+ ${HARU_INCLUDE_DIRS}
+ )
+ list(APPEND LIB
+ ${HARU_LIBRARIES}
+ )
+ add_definitions(-DWITH_HARU)
+endif()
+
+
+list(APPEND LIB
+ ${BOOST_LIBRARIES}
+)
+
+blender_add_lib(bf_gpencil "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/io/gpencil/gpencil_io.h b/source/blender/io/gpencil/gpencil_io.h
new file mode 100644
index 00000000000..f4b2e59f8c5
--- /dev/null
+++ b/source/blender/io/gpencil/gpencil_io.h
@@ -0,0 +1,92 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ARegion;
+struct bContext;
+struct Object;
+struct View3D;
+
+typedef struct GpencilIOParams {
+ bContext *C;
+ ARegion *region;
+ View3D *v3d;
+ /** Grease pencil object. */
+ Object *ob;
+ /** Mode (see eGpencilIO_Modes). */
+ uint16_t mode;
+ int32_t frame_start;
+ int32_t frame_end;
+ int32_t frame_cur;
+ uint32_t flag;
+ float scale;
+ /** Select mode (see eGpencilExportSelect). */
+ uint16_t select_mode;
+ /** Frame mode (see eGpencilExportFrame). */
+ uint16_t frame_mode;
+ /** Stroke sampling factor. */
+ float stroke_sample;
+ int32_t resolution;
+} GpencilIOParams;
+
+/* GpencilIOParams->flag. */
+typedef enum eGpencilIOParams_Flag {
+ /* Export Filled strokes. */
+ GP_EXPORT_FILL = (1 << 0),
+ /* Export normalized thickness. */
+ GP_EXPORT_NORM_THICKNESS = (1 << 1),
+ /* Clip camera area. */
+ GP_EXPORT_CLIP_CAMERA = (1 << 2),
+} eGpencilIOParams_Flag;
+
+typedef enum eGpencilIO_Modes {
+ GP_EXPORT_TO_SVG = 0,
+ GP_EXPORT_TO_PDF = 1,
+
+ GP_IMPORT_FROM_SVG = 2,
+ /* Add new formats here. */
+} eGpencilIO_Modes;
+
+/* Object to be exported. */
+typedef enum eGpencilExportSelect {
+ GP_EXPORT_ACTIVE = 0,
+ GP_EXPORT_SELECTED = 1,
+ GP_EXPORT_VISIBLE = 2,
+} eGpencilExportSelect;
+
+/* Framerange to be exported. */
+typedef enum eGpencilExportFrame {
+ GP_EXPORT_FRAME_ACTIVE = 0,
+ GP_EXPORT_FRAME_SELECTED = 1,
+} eGpencilExportFrame;
+
+bool gpencil_io_export(const char *filename, struct GpencilIOParams *iparams);
+bool gpencil_io_import(const char *filename, struct GpencilIOParams *iparams);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.cc b/source/blender/io/gpencil/intern/gpencil_io_base.cc
new file mode 100644
index 00000000000..855252e648c
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.cc
@@ -0,0 +1,386 @@
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_path_util.h"
+#include "BLI_span.hh"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_layer_types.h"
+#include "DNA_material_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_camera.h"
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "UI_view2d.h"
+
+#include "ED_view3d.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "gpencil_io_base.h"
+
+using blender::Span;
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilIO::GpencilIO(const GpencilIOParams *iparams)
+{
+ params_ = *iparams;
+
+ /* Easy access data. */
+ bmain_ = CTX_data_main(params_.C);
+ depsgraph_ = CTX_data_depsgraph_pointer(params_.C);
+ scene_ = CTX_data_scene(params_.C);
+ rv3d_ = (RegionView3D *)params_.region->regiondata;
+ gpd_ = (params_.ob != nullptr) ? (bGPdata *)params_.ob->data : nullptr;
+ cfra_ = iparams->frame_cur;
+
+ /* Calculate camera matrix. */
+ Object *cam_ob = params_.v3d->camera;
+ if (cam_ob != nullptr) {
+ /* Set up parameters. */
+ CameraParams params;
+ BKE_camera_params_init(&params);
+ BKE_camera_params_from_object(&params, cam_ob);
+
+ /* Compute matrix, viewplane, .. */
+ RenderData *rd = &scene_->r;
+ BKE_camera_params_compute_viewplane(&params, rd->xsch, rd->ysch, rd->xasp, rd->yasp);
+ BKE_camera_params_compute_matrix(&params);
+
+ float viewmat[4][4];
+ invert_m4_m4(viewmat, cam_ob->obmat);
+
+ mul_m4_m4m4(persmat_, params.winmat, viewmat);
+ }
+ else {
+ unit_m4(persmat_);
+ }
+
+ winx_ = params_.region->winx;
+ winy_ = params_.region->winy;
+
+ /* Camera rectangle. */
+ if (rv3d_->persp == RV3D_CAMOB) {
+ render_x_ = (scene_->r.xsch * scene_->r.size) / 100;
+ render_y_ = (scene_->r.ysch * scene_->r.size) / 100;
+
+ ED_view3d_calc_camera_border(CTX_data_scene(params_.C),
+ depsgraph_,
+ params_.region,
+ params_.v3d,
+ rv3d_,
+ &camera_rect_,
+ true);
+ is_camera_ = true;
+ camera_ratio_ = render_x_ / (camera_rect_.xmax - camera_rect_.xmin);
+ offset_.x = camera_rect_.xmin;
+ offset_.y = camera_rect_.ymin;
+ }
+ else {
+ is_camera_ = false;
+ /* Calc selected object boundbox. Need set initial value to some variables. */
+ camera_ratio_ = 1.0f;
+ offset_.x = 0.0f;
+ offset_.y = 0.0f;
+
+ selected_objects_boundbox_calc();
+ rctf boundbox;
+ selected_objects_boundbox_get(&boundbox);
+
+ render_x_ = boundbox.xmax - boundbox.xmin;
+ render_y_ = boundbox.ymax - boundbox.ymin;
+ offset_.x = boundbox.xmin;
+ offset_.y = boundbox.ymin;
+ }
+}
+
+/** Create a list of selected objects sorted from back to front */
+void GpencilIO::create_object_list()
+{
+ ViewLayer *view_layer = CTX_data_view_layer(params_.C);
+
+ float3 camera_z_axis;
+ copy_v3_v3(camera_z_axis, rv3d_->viewinv[2]);
+ ob_list_.clear();
+
+ LISTBASE_FOREACH (Base *, base, &view_layer->object_bases) {
+ Object *object = base->object;
+
+ if (object->type != OB_GPENCIL) {
+ continue;
+ }
+ if ((params_.select_mode == GP_EXPORT_ACTIVE) && (params_.ob != object)) {
+ continue;
+ }
+
+ if ((params_.select_mode == GP_EXPORT_SELECTED) && ((base->flag & BASE_SELECTED) == 0)) {
+ continue;
+ }
+
+ /* Save z-depth from view to sort from back to front. */
+ if (is_camera_) {
+ float camera_z = dot_v3v3(camera_z_axis, object->obmat[3]);
+ ObjectZ obz = {camera_z, object};
+ ob_list_.append(obz);
+ }
+ else {
+ float zdepth = 0;
+ if (rv3d_) {
+ if (rv3d_->is_persp) {
+ zdepth = ED_view3d_calc_zfac(rv3d_, object->obmat[3], nullptr);
+ }
+ else {
+ zdepth = -dot_v3v3(rv3d_->viewinv[2], object->obmat[3]);
+ }
+ ObjectZ obz = {zdepth * -1.0f, object};
+ ob_list_.append(obz);
+ }
+ }
+ }
+ /* Sort list of objects from point of view. */
+ std::sort(ob_list_.begin(), ob_list_.end(), [](const ObjectZ &obz1, const ObjectZ &obz2) {
+ return obz1.zdepth < obz2.zdepth;
+ });
+}
+
+/**
+ * Set file input_text full path.
+ * \param filename: Path of the file provided by save dialog.
+ */
+void GpencilIO::filename_set(const char *filename)
+{
+ BLI_strncpy(filename_, filename, FILE_MAX);
+ BLI_path_abs(filename_, BKE_main_blendfile_path(bmain_));
+}
+
+/** Convert to screenspace. */
+bool GpencilIO::gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co)
+{
+ float3 parent_co = diff_mat_ * co;
+ float2 screen_co;
+ eV3DProjTest test = (eV3DProjTest)(V3D_PROJ_RET_OK);
+ if (ED_view3d_project_float_global(params_.region, parent_co, screen_co, test) ==
+ V3D_PROJ_RET_OK) {
+ if (!ELEM(V2D_IS_CLIPPED, screen_co[0], screen_co[1])) {
+ copy_v2_v2(r_co, screen_co);
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+ /* Apply offset and scale. */
+ sub_v2_v2(r_co, &offset_.x);
+ mul_v2_fl(r_co, camera_ratio_);
+
+ return true;
+ }
+ }
+ r_co[0] = V2D_IS_CLIPPED;
+ r_co[1] = V2D_IS_CLIPPED;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co[0] = winx_ - r_co[0];
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co[1] = winy_ - r_co[1];
+ }
+
+ return false;
+}
+
+/** Convert to render space. */
+float2 GpencilIO::gpencil_3D_point_to_render_space(const float3 co)
+{
+ float3 parent_co = diff_mat_ * co;
+ mul_m4_v3(persmat_, parent_co);
+
+ parent_co.x = parent_co.x / max_ff(FLT_MIN, parent_co[2]);
+ parent_co.y = parent_co.y / max_ff(FLT_MIN, parent_co[2]);
+
+ float2 r_co;
+ r_co.x = (parent_co.x + 1.0f) / 2.0f * (float)render_x_;
+ r_co.y = (parent_co.y + 1.0f) / 2.0f * (float)render_y_;
+
+ /* Invert X axis. */
+ if (invert_axis_[0]) {
+ r_co.x = (float)render_x_ - r_co.x;
+ }
+ /* Invert Y axis. */
+ if (invert_axis_[1]) {
+ r_co.y = (float)render_y_ - r_co.y;
+ }
+
+ return r_co;
+}
+
+/** Convert to 2D. */
+float2 GpencilIO::gpencil_3D_point_to_2D(const float3 co)
+{
+ const bool is_camera = (bool)(rv3d_->persp == RV3D_CAMOB);
+ if (is_camera) {
+ return gpencil_3D_point_to_render_space(co);
+ }
+ float2 result;
+ gpencil_3D_point_to_screen_space(co, result);
+ return result;
+}
+
+/** Get radius of point. */
+float GpencilIO::stroke_point_radius_get(bGPDlayer *gpl, bGPDstroke *gps)
+{
+ bGPDspoint *pt = &gps->points[0];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+
+ /* Radius. */
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps, 3, diff_mat_.values);
+
+ pt = &gps_perimeter->points[0];
+ const float2 screen_ex = gpencil_3D_point_to_2D(&pt->x);
+
+ const float2 v1 = screen_co - screen_ex;
+ float radius = v1.length();
+ BKE_gpencil_free_stroke(gps_perimeter);
+
+ return MAX2(radius, 1.0f);
+}
+
+void GpencilIO::prepare_layer_export_matrix(Object *ob, bGPDlayer *gpl)
+{
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob, gpl, diff_mat_.values);
+ diff_mat_ = diff_mat_ * float4x4(gpl->layer_invmat);
+}
+
+void GpencilIO::prepare_stroke_export_colors(Object *ob, bGPDstroke *gps)
+{
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob, gps->mat_nr + 1);
+
+ /* Stroke color. */
+ copy_v4_v4(stroke_color_, gp_style->stroke_rgba);
+ avg_opacity_ = 0;
+ /* Get average vertex color and apply. */
+ float avg_color[4] = {0.0f, 0.0f, 0.0f, 0.0f};
+ for (const bGPDspoint &pt : Span(gps->points, gps->totpoints)) {
+ add_v4_v4(avg_color, pt.vert_color);
+ avg_opacity_ += pt.strength;
+ }
+
+ mul_v4_v4fl(avg_color, avg_color, 1.0f / (float)gps->totpoints);
+ interp_v3_v3v3(stroke_color_, stroke_color_, avg_color, avg_color[3]);
+ avg_opacity_ /= (float)gps->totpoints;
+
+ /* Fill color. */
+ copy_v4_v4(fill_color_, gp_style->fill_rgba);
+ /* Apply vertex color for fill. */
+ interp_v3_v3v3(fill_color_, fill_color_, gps->vert_color_fill, gps->vert_color_fill[3]);
+}
+
+float GpencilIO::stroke_average_opacity_get()
+{
+ return avg_opacity_;
+}
+
+bool GpencilIO::is_camera_mode()
+{
+ return is_camera_;
+}
+
+/* Calculate selected strokes boundbox. */
+void GpencilIO::selected_objects_boundbox_calc()
+{
+ const float gap = 10.0f;
+
+ float2 min, max;
+ INIT_MINMAX2(min, max);
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ BKE_gpencil_layer_transform_matrix_get(depsgraph_, ob_eval, gpl, diff_mat_.values);
+
+ bGPDframe *gpf = gpl->actframe;
+ if (gpf == nullptr) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints == 0) {
+ continue;
+ }
+ for (const bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
+ minmax_v2v2_v2(min, max, screen_co);
+ }
+ }
+ }
+ }
+ /* Add small gap. */
+ add_v2_fl(min, gap * -1.0f);
+ add_v2_fl(max, gap);
+
+ select_boundbox_.xmin = min[0];
+ select_boundbox_.ymin = min[1];
+ select_boundbox_.xmax = max[0];
+ select_boundbox_.ymax = max[1];
+}
+
+void GpencilIO::selected_objects_boundbox_get(rctf *boundbox)
+{
+ boundbox->xmin = select_boundbox_.xmin;
+ boundbox->xmax = select_boundbox_.xmax;
+ boundbox->ymin = select_boundbox_.ymin;
+ boundbox->ymax = select_boundbox_.ymax;
+}
+
+void GpencilIO::frame_number_set(const int value)
+{
+ cfra_ = value;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_base.h b/source/blender/io/gpencil/intern/gpencil_io_base.h
new file mode 100644
index 00000000000..986221618b7
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_base.h
@@ -0,0 +1,116 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float2.hh"
+#include "BLI_float3.hh"
+#include "BLI_float4x4.hh"
+#include "BLI_vector.hh"
+
+#include "DNA_space_types.h" /* for FILE_MAX */
+
+#include "gpencil_io.h"
+
+struct Depsgraph;
+struct Main;
+struct Object;
+struct RegionView3D;
+struct Scene;
+
+struct bGPdata;
+struct bGPDlayer;
+struct bGPDstroke;
+
+using blender::Vector;
+
+namespace blender::io::gpencil {
+
+class GpencilIO {
+ public:
+ GpencilIO(const GpencilIOParams *iparams);
+
+ void frame_number_set(const int value);
+
+ protected:
+ GpencilIOParams params_;
+
+ bool invert_axis_[2];
+ float4x4 diff_mat_;
+ char filename_[FILE_MAX];
+
+ /* Used for sorting objects. */
+ struct ObjectZ {
+ float zdepth;
+ struct Object *ob;
+ };
+
+ /** List of included objects. */
+ blender::Vector<ObjectZ> ob_list_;
+
+ /* Data for easy access. */
+ struct Depsgraph *depsgraph_;
+ struct bGPdata *gpd_;
+ struct Main *bmain_;
+ struct Scene *scene_;
+ struct RegionView3D *rv3d_;
+
+ int16_t winx_, winy_;
+ int16_t render_x_, render_y_;
+ float camera_ratio_;
+ rctf camera_rect_;
+
+ float2 offset_;
+
+ int cfra_;
+
+ float stroke_color_[4], fill_color_[4];
+
+ /* Geometry functions. */
+ bool gpencil_3D_point_to_screen_space(const float3 co, float2 &r_co);
+ float2 gpencil_3D_point_to_render_space(const float3 co);
+ float2 gpencil_3D_point_to_2D(const float3 co);
+
+ float stroke_point_radius_get(struct bGPDlayer *gpl, struct bGPDstroke *gps);
+ void create_object_list();
+
+ bool is_camera_mode();
+
+ float stroke_average_opacity_get();
+
+ void prepare_layer_export_matrix(struct Object *ob, struct bGPDlayer *gpl);
+ void prepare_stroke_export_colors(struct Object *ob, struct bGPDstroke *gps);
+
+ void selected_objects_boundbox_calc();
+ void selected_objects_boundbox_get(rctf *boundbox);
+ void filename_set(const char *filename);
+
+ private:
+ float avg_opacity_;
+ bool is_camera_;
+ rctf select_boundbox_;
+
+ /* Camera matrix. */
+ float persmat_[4][4];
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_capi.cc b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
new file mode 100644
index 00000000000..34539a66fe8
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_capi.cc
@@ -0,0 +1,202 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include <stdio.h>
+
+#include "BLI_listbase.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_space_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_main.h"
+#include "BKE_scene.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "../gpencil_io.h"
+
+#ifdef WITH_HARU
+# include "gpencil_io_export_pdf.h"
+#endif
+
+#ifdef WITH_PUGIXML
+# include "gpencil_io_export_svg.h"
+#endif
+
+#include "gpencil_io_import_svg.h"
+
+#ifdef WITH_HARU
+using blender::io::gpencil::GpencilExporterPDF;
+#endif
+#ifdef WITH_PUGIXML
+using blender::io::gpencil::GpencilExporterSVG;
+#endif
+using blender::io::gpencil::GpencilImporterSVG;
+
+/* Check if frame is included. */
+static bool is_keyframe_included(bGPdata *gpd_, const int32_t framenum, const bool use_selected)
+{
+ /* Check if exist a frame. */
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ if (gpf->framenum == framenum) {
+ if ((!use_selected) || (use_selected && (gpf->flag & GP_FRAME_SELECT))) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+}
+
+/* Import frame. */
+static bool gpencil_io_import_frame(void *in_importer, const GpencilIOParams &iparams)
+{
+
+ bool result = false;
+ switch (iparams.mode) {
+ case GP_IMPORT_FROM_SVG: {
+ GpencilImporterSVG *importer = (GpencilImporterSVG *)in_importer;
+ result |= importer->read();
+ break;
+ }
+ /* Add new import formats here. */
+ default:
+ break;
+ }
+
+ return result;
+}
+
+/* Export frame in PDF. */
+#ifdef WITH_HARU
+static bool gpencil_io_export_pdf(Depsgraph *depsgraph,
+ Scene *scene,
+ Object *ob,
+ GpencilExporterPDF *exporter,
+ const GpencilIOParams *iparams)
+{
+ bool result = false;
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ exporter->frame_number_set(iparams->frame_cur);
+ result |= exporter->new_document();
+
+ const bool use_frame_selected = (iparams->frame_mode == GP_EXPORT_FRAME_SELECTED);
+ if (use_frame_selected) {
+ for (int32_t i = iparams->frame_start; i < iparams->frame_end + 1; i++) {
+ if (!is_keyframe_included(gpd_eval, i, use_frame_selected)) {
+ continue;
+ }
+
+ CFRA = i;
+ BKE_scene_graph_update_for_newframe(depsgraph);
+ exporter->frame_number_set(i);
+ exporter->add_newpage();
+ exporter->add_body();
+ }
+ result = exporter->write();
+ /* Back to original frame. */
+ exporter->frame_number_set(iparams->frame_cur);
+ CFRA = iparams->frame_cur;
+ BKE_scene_graph_update_for_newframe(depsgraph);
+ }
+ else {
+ exporter->add_newpage();
+ exporter->add_body();
+ result = exporter->write();
+ }
+
+ return result;
+}
+#endif
+
+/* Export current frame in SVG. */
+#ifdef WITH_PUGIXML
+static bool gpencil_io_export_frame_svg(GpencilExporterSVG *exporter,
+ const GpencilIOParams *iparams,
+ const bool newpage,
+ const bool body,
+ const bool savepage)
+{
+ bool result = false;
+ exporter->frame_number_set(iparams->frame_cur);
+ if (newpage) {
+ result |= exporter->add_newpage();
+ }
+ if (body) {
+ result |= exporter->add_body();
+ }
+ if (savepage) {
+ result = exporter->write();
+ }
+ return result;
+}
+#endif
+
+/* Main import entry point function. */
+bool gpencil_io_import(const char *filename, GpencilIOParams *iparams)
+{
+ GpencilImporterSVG importer = GpencilImporterSVG(filename, iparams);
+
+ return gpencil_io_import_frame(&importer, *iparams);
+}
+
+/* Main export entry point function. */
+bool gpencil_io_export(const char *filename, GpencilIOParams *iparams)
+{
+ Depsgraph *depsgraph_ = CTX_data_depsgraph_pointer(iparams->C);
+ Scene *scene_ = CTX_data_scene(iparams->C);
+ Object *ob = CTX_data_active_object(iparams->C);
+
+ UNUSED_VARS(depsgraph_, scene_, ob);
+
+ switch (iparams->mode) {
+#ifdef WITH_PUGIXML
+ case GP_EXPORT_TO_SVG: {
+ GpencilExporterSVG exporter = GpencilExporterSVG(filename, iparams);
+ return gpencil_io_export_frame_svg(&exporter, iparams, true, true, true);
+ break;
+ }
+#endif
+#ifdef WITH_HARU
+ case GP_EXPORT_TO_PDF: {
+ GpencilExporterPDF exporter = GpencilExporterPDF(filename, iparams);
+ return gpencil_io_export_pdf(depsgraph_, scene_, ob, &exporter, iparams);
+ break;
+ }
+#endif
+ /* Add new export formats here. */
+ default:
+ break;
+ }
+ return false;
+}
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_base.h b/source/blender/io/gpencil/intern/gpencil_io_export_base.h
new file mode 100644
index 00000000000..19a24a75fd2
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_base.h
@@ -0,0 +1,38 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_base.h"
+
+namespace blender::io::gpencil {
+
+class GpencilExporter : public GpencilIO {
+
+ public:
+ GpencilExporter(const struct GpencilIOParams *iparams) : GpencilIO(iparams){};
+ virtual bool write() = 0;
+
+ protected:
+ private:
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc
new file mode 100644
index 00000000000..ba16d635c2d
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.cc
@@ -0,0 +1,311 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_math_vector.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+#include "DNA_view3d_types.h"
+
+#include "BKE_context.h"
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include "UI_view2d.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_export_pdf.h"
+
+namespace blender ::io ::gpencil {
+
+static void error_handler(HPDF_STATUS error_no, HPDF_STATUS detail_no, void *UNUSED(user_data))
+{
+ printf("ERROR: error_no=%04X, detail_no=%u\n", (HPDF_UINT)error_no, (HPDF_UINT)detail_no);
+}
+
+/* Constructor. */
+GpencilExporterPDF::GpencilExporterPDF(const char *filename, const GpencilIOParams *iparams)
+ : GpencilExporter(iparams)
+{
+ filename_set(filename);
+
+ invert_axis_[0] = false;
+ invert_axis_[1] = false;
+
+ pdf_ = nullptr;
+ page_ = nullptr;
+ gstate_ = nullptr;
+}
+
+bool GpencilExporterPDF::new_document()
+{
+ return create_document();
+}
+
+bool GpencilExporterPDF::add_newpage()
+{
+ return add_page();
+}
+
+bool GpencilExporterPDF::add_body()
+{
+ export_gpencil_layers();
+ return true;
+}
+
+bool GpencilExporterPDF::write()
+{
+ /* Support unicode character paths on Windows. */
+ HPDF_STATUS res = 0;
+ /* TODO: It looks libharu does not support unicode. */
+ //#ifdef WIN32
+ // char filename_cstr[FILE_MAX];
+ // BLI_strncpy(filename_cstr, filename_, FILE_MAX);
+ //
+ // UTF16_ENCODE(filename_cstr);
+ // std::wstring wstr(filename_cstr_16);
+ // res = HPDF_SaveToFile(pdf_, wstr.c_str());
+ //
+ // UTF16_UN_ENCODE(filename_cstr);
+ //#else
+ res = HPDF_SaveToFile(pdf_, filename_);
+ //#endif
+
+ return (res == 0) ? true : false;
+}
+
+/* Create pdf document. */
+bool GpencilExporterPDF::create_document()
+{
+ pdf_ = HPDF_New(error_handler, nullptr);
+ if (!pdf_) {
+ std::cout << "error: cannot create PdfDoc object\n";
+ return false;
+ }
+ return true;
+}
+
+/* Add page. */
+bool GpencilExporterPDF::add_page()
+{
+ /* Add a new page object. */
+ page_ = HPDF_AddPage(pdf_);
+ if (!pdf_) {
+ std::cout << "error: cannot create PdfPage\n";
+ return false;
+ }
+
+ HPDF_Page_SetWidth(page_, render_x_);
+ HPDF_Page_SetHeight(page_, render_y_);
+
+ return true;
+}
+
+/* Main layer loop. */
+void GpencilExporterPDF::export_gpencil_layers()
+{
+ /* If is doing a set of frames, the list of objects can change for each frame. */
+ create_object_list();
+
+ const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0);
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ prepare_layer_export_matrix(ob, gpl);
+
+ bGPDframe *gpf = gpl->actframe;
+ if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
+ continue;
+ }
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints < 2) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_material_visible(ob, gps)) {
+ continue;
+ }
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
+ gps_duplicate->mat_nr + 1);
+
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
+ (gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
+ (gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ prepare_stroke_export_colors(ob, gps_duplicate);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+ /* Fill. */
+ if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
+ /* Fill is exported as polygon for fill and stroke in a different shape. */
+ export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, true, false);
+ }
+
+ /* Stroke. */
+ if (is_stroke) {
+ if (is_normalized) {
+ export_stroke_to_polyline(gpl, gps_duplicate, is_stroke, false, true);
+ }
+ else {
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
+
+ /* Sample stroke. */
+ if (params_.stroke_sample > 0.0f) {
+ BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
+ }
+
+ export_stroke_to_polyline(gpl, gps_perimeter, is_stroke, false, false);
+
+ BKE_gpencil_free_stroke(gps_perimeter);
+ }
+ }
+ BKE_gpencil_free_stroke(gps_duplicate);
+ }
+ }
+ }
+}
+
+/**
+ * Export a stroke using polyline or polygon
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterPDF::export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const bool is_stroke,
+ const bool do_fill,
+ const bool normalize)
+{
+ const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
+ const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
+
+ /* Get the thickness in pixels using a simple 1 point stroke. */
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
+ gps_temp->totpoints = 1;
+ gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
+ const bGPDspoint *pt_src = &gps->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[0];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = avg_pressure;
+
+ const float radius = stroke_point_radius_get(gpl, gps_temp);
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ color_set(gpl, do_fill);
+
+ if (is_stroke && !do_fill) {
+ HPDF_Page_SetLineJoin(page_, HPDF_ROUND_JOIN);
+ HPDF_Page_SetLineWidth(page_, MAX2((radius * 2.0f) - gpl->line_change, 1.0f));
+ }
+
+ /* Loop all points. */
+ for (const int i : IndexRange(gps->totpoints)) {
+ bGPDspoint *pt = &gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+ if (i == 0) {
+ HPDF_Page_MoveTo(page_, screen_co.x, screen_co.y);
+ }
+ else {
+ HPDF_Page_LineTo(page_, screen_co.x, screen_co.y);
+ }
+ }
+ /* Close cyclic */
+ if (cyclic) {
+ HPDF_Page_ClosePath(page_);
+ }
+
+ if (do_fill || !normalize) {
+ HPDF_Page_Fill(page_);
+ }
+ else {
+ HPDF_Page_Stroke(page_);
+ }
+
+ HPDF_Page_GRestore(page_);
+}
+
+/**
+ * Set color
+ * @param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterPDF::color_set(bGPDlayer *gpl, const bool do_fill)
+{
+ const float fill_opacity = fill_color_[3] * gpl->opacity;
+ const float stroke_opacity = stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity;
+
+ HPDF_Page_GSave(page_);
+ gstate_ = HPDF_CreateExtGState(pdf_);
+
+ float col[3];
+ if (do_fill) {
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ CLAMP3(col, 0.0f, 1.0f);
+
+ HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(fill_opacity, 0.0f, 1.0f));
+ HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
+ }
+ else {
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ CLAMP3(col, 0.0f, 1.0f);
+
+ HPDF_ExtGState_SetAlphaFill(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
+ HPDF_ExtGState_SetAlphaStroke(gstate_, clamp_f(stroke_opacity, 0.0f, 1.0f));
+ HPDF_Page_SetRGBFill(page_, col[0], col[1], col[2]);
+ HPDF_Page_SetRGBStroke(page_, col[0], col[1], col[2]);
+ }
+ HPDF_Page_SetExtGState(page_, gstate_);
+}
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h
new file mode 100644
index 00000000000..009c05a8b49
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_pdf.h
@@ -0,0 +1,67 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "gpencil_io_export_base.h"
+#include "hpdf.h"
+
+struct GpencilIOParams;
+struct bGPDlayer;
+struct bGPDstroke;
+
+#define PDF_EXPORTER_NAME "PDF Exporter for Grease Pencil"
+#define PDF_EXPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilExporterPDF : public GpencilExporter {
+
+ public:
+ GpencilExporterPDF(const char *filename, const struct GpencilIOParams *iparams);
+ bool new_document();
+ bool add_newpage();
+ bool add_body();
+ bool write();
+
+ protected:
+ private:
+ /* PDF document. */
+ HPDF_Doc pdf_;
+ /* PDF page. */
+ HPDF_Page page_;
+ /* State. */
+ HPDF_ExtGState gstate_;
+
+ bool create_document();
+ bool add_page();
+ void export_gpencil_layers();
+
+ void export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ const bool is_stroke,
+ const bool do_fill,
+ const bool normalize);
+ void color_set(bGPDlayer *gpl, const bool do_fill);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc
new file mode 100644
index 00000000000..89584cd242f
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.cc
@@ -0,0 +1,464 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_math_vector.h"
+#include "BLI_string.h"
+#include "BLI_utildefines.h"
+
+#include "DNA_gpencil_types.h"
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+#include "DNA_screen_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+#include "BKE_main.h"
+#include "BKE_material.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+#include "ED_view3d.h"
+
+#ifdef WIN32
+# include "utfconv.h"
+#endif
+
+#include "UI_view2d.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_export_svg.h"
+
+#include "pugixml.hpp"
+
+namespace blender ::io ::gpencil {
+
+/* Constructor. */
+GpencilExporterSVG::GpencilExporterSVG(const char *filename, const GpencilIOParams *iparams)
+ : GpencilExporter(iparams)
+{
+ filename_set(filename);
+
+ invert_axis_[0] = false;
+ invert_axis_[1] = true;
+}
+
+bool GpencilExporterSVG::add_newpage()
+{
+ create_document_header();
+ return true;
+}
+
+bool GpencilExporterSVG::add_body()
+{
+ export_gpencil_layers();
+ return true;
+}
+
+bool GpencilExporterSVG::write()
+{
+ bool result = true;
+/* Support unicode character paths on Windows. */
+#ifdef WIN32
+ char filename_cstr[FILE_MAX];
+ BLI_strncpy(filename_cstr, filename_, FILE_MAX);
+
+ UTF16_ENCODE(filename_cstr);
+ std::wstring wstr(filename_cstr_16);
+ result = main_doc_.save_file(wstr.c_str());
+
+ UTF16_UN_ENCODE(filename_cstr);
+#else
+ result = main_doc_.save_file(filename_);
+#endif
+
+ return result;
+}
+
+/* Create document header and main svg node. */
+void GpencilExporterSVG::create_document_header()
+{
+ /* Add a custom document declaration node. */
+ pugi::xml_node decl = main_doc_.prepend_child(pugi::node_declaration);
+ decl.append_attribute("version") = "1.0";
+ decl.append_attribute("encoding") = "UTF-8";
+
+ pugi::xml_node comment = main_doc_.append_child(pugi::node_comment);
+ char txt[128];
+ sprintf(txt, " Generator: Blender, %s - %s ", SVG_EXPORTER_NAME, SVG_EXPORTER_VERSION);
+ comment.set_value(txt);
+
+ pugi::xml_node doctype = main_doc_.append_child(pugi::node_doctype);
+ doctype.set_value(
+ "svg PUBLIC \"-//W3C//DTD SVG 1.1//EN\" "
+ "\"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\"");
+
+ main_node_ = main_doc_.append_child("svg");
+ main_node_.append_attribute("version").set_value("1.0");
+ main_node_.append_attribute("x").set_value("0px");
+ main_node_.append_attribute("y").set_value("0px");
+
+ std::string width;
+ std::string height;
+
+ width = std::to_string(render_x_);
+ height = std::to_string(render_y_);
+
+ main_node_.append_attribute("width").set_value((width + "px").c_str());
+ main_node_.append_attribute("height").set_value((height + "px").c_str());
+ std::string viewbox = "0 0 " + width + " " + height;
+ main_node_.append_attribute("viewBox").set_value(viewbox.c_str());
+}
+
+/* Main layer loop. */
+void GpencilExporterSVG::export_gpencil_layers()
+{
+ const bool is_clipping = is_camera_mode() && (params_.flag & GP_EXPORT_CLIP_CAMERA) != 0;
+
+ /* If is doing a set of frames, the list of objects can change for each frame. */
+ create_object_list();
+
+ for (ObjectZ &obz : ob_list_) {
+ Object *ob = obz.ob;
+
+ /* Camera clipping. */
+ if (is_clipping) {
+ pugi::xml_node clip_node = main_node_.append_child("clipPath");
+ clip_node.append_attribute("id").set_value(("clip-path" + std::to_string(cfra_)).c_str());
+
+ add_rect(clip_node, 0, 0, render_x_, render_y_, 0.0f, "#000000");
+ }
+
+ frame_node_ = main_node_.append_child("g");
+ std::string frametxt = "blender_frame_" + std::to_string(cfra_);
+ frame_node_.append_attribute("id").set_value(frametxt.c_str());
+
+ /* Clip area. */
+ if (is_clipping) {
+ frame_node_.append_attribute("clip-path")
+ .set_value(("url(#clip-path" + std::to_string(cfra_) + ")").c_str());
+ }
+
+ pugi::xml_node ob_node = frame_node_.append_child("g");
+
+ char obtxt[96];
+ sprintf(obtxt, "blender_object_%s", ob->id.name + 2);
+ ob_node.append_attribute("id").set_value(obtxt);
+
+ /* Use evaluated version to get strokes with modifiers. */
+ Object *ob_eval_ = (Object *)DEG_get_evaluated_id(depsgraph_, &ob->id);
+ bGPdata *gpd_eval = (bGPdata *)ob_eval_->data;
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_eval->layers) {
+ if (gpl->flag & GP_LAYER_HIDE) {
+ continue;
+ }
+ prepare_layer_export_matrix(ob, gpl);
+
+ bGPDframe *gpf = gpl->actframe;
+ if ((gpf == nullptr) || (gpf->strokes.first == nullptr)) {
+ continue;
+ }
+
+ /* Layer node. */
+ std::string txt = "Layer: ";
+ txt.append(gpl->info);
+ ob_node.append_child(pugi::node_comment).set_value(txt.c_str());
+
+ pugi::xml_node node_gpl = ob_node.append_child("g");
+ node_gpl.append_attribute("id").set_value(gpl->info);
+
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ if (gps->totpoints < 2) {
+ continue;
+ }
+ if (!ED_gpencil_stroke_material_visible(ob, gps)) {
+ continue;
+ }
+
+ /* Duplicate the stroke to apply any layer thickness change. */
+ bGPDstroke *gps_duplicate = BKE_gpencil_stroke_duplicate(gps, true, false);
+
+ MaterialGPencilStyle *gp_style = BKE_gpencil_material_settings(ob,
+ gps_duplicate->mat_nr + 1);
+
+ const bool is_stroke = ((gp_style->flag & GP_MATERIAL_STROKE_SHOW) &&
+ (gp_style->stroke_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+ const bool is_fill = ((gp_style->flag & GP_MATERIAL_FILL_SHOW) &&
+ (gp_style->fill_rgba[3] > GPENCIL_ALPHA_OPACITY_THRESH));
+
+ prepare_stroke_export_colors(ob, gps_duplicate);
+
+ /* Apply layer thickness change. */
+ gps_duplicate->thickness += gpl->line_change;
+ /* Apply object scale to thickness. */
+ gps_duplicate->thickness *= mat4_to_scale(ob->obmat);
+ CLAMP_MIN(gps_duplicate->thickness, 1.0f);
+
+ const bool is_normalized = ((params_.flag & GP_EXPORT_NORM_THICKNESS) != 0) ||
+ BKE_gpencil_stroke_is_pressure_constant(gps);
+
+ /* Fill. */
+ if ((is_fill) && (params_.flag & GP_EXPORT_FILL)) {
+ /* Fill is always exported as polygon because the stroke of the fill is done
+ * in a different SVG command. */
+ export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, true);
+ }
+
+ /* Stroke. */
+ if (is_stroke) {
+ if (is_normalized) {
+ export_stroke_to_polyline(gpl, gps_duplicate, node_gpl, is_stroke, false);
+ }
+ else {
+ bGPDstroke *gps_perimeter = BKE_gpencil_stroke_perimeter_from_view(
+ rv3d_, gpd_, gpl, gps_duplicate, 3, diff_mat_.values);
+
+ /* Sample stroke. */
+ if (params_.stroke_sample > 0.0f) {
+ BKE_gpencil_stroke_sample(gpd_eval, gps_perimeter, params_.stroke_sample, false);
+ }
+
+ export_stroke_to_path(gpl, gps_perimeter, node_gpl, false);
+
+ BKE_gpencil_free_stroke(gps_perimeter);
+ }
+ }
+
+ BKE_gpencil_free_stroke(gps_duplicate);
+ }
+ }
+ }
+}
+
+/**
+ * Export a stroke using SVG path
+ * \param node_gpl: Node of the layer.
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::export_stroke_to_path(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool do_fill)
+{
+ pugi::xml_node node_gps = node_gpl.append_child("path");
+
+ float col[3];
+ std::string stroke_hex;
+ if (do_fill) {
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ }
+ else {
+ node_gps.append_attribute("fill-opacity")
+ .set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
+
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ }
+
+ linearrgb_to_srgb_v3_v3(col, col);
+ stroke_hex = rgb_to_hexstr(col);
+
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke").set_value("none");
+
+ std::string txt = "M";
+ for (const int i : IndexRange(gps->totpoints)) {
+ if (i > 0) {
+ txt.append("L");
+ }
+ bGPDspoint &pt = gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt.x);
+ txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
+ }
+ /* Close patch (cyclic)*/
+ if (gps->flag & GP_STROKE_CYCLIC) {
+ txt.append("z");
+ }
+
+ node_gps.append_attribute("d").set_value(txt.c_str());
+}
+
+/**
+ * Export a stroke using polyline or polygon
+ * \param node_gpl: Node of the layer.
+ * \param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::export_stroke_to_polyline(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_stroke,
+ const bool do_fill)
+{
+ const bool cyclic = ((gps->flag & GP_STROKE_CYCLIC) != 0);
+ const float avg_pressure = BKE_gpencil_stroke_average_pressure_get(gps);
+
+ /* Get the thickness in pixels using a simple 1 point stroke. */
+ bGPDstroke *gps_temp = BKE_gpencil_stroke_duplicate(gps, false, false);
+ gps_temp->totpoints = 1;
+ gps_temp->points = (bGPDspoint *)MEM_callocN(sizeof(bGPDspoint), "gp_stroke_points");
+ bGPDspoint *pt_src = &gps->points[0];
+ bGPDspoint *pt_dst = &gps_temp->points[0];
+ copy_v3_v3(&pt_dst->x, &pt_src->x);
+ pt_dst->pressure = avg_pressure;
+
+ const float radius = stroke_point_radius_get(gpl, gps_temp);
+
+ BKE_gpencil_free_stroke(gps_temp);
+
+ pugi::xml_node node_gps = node_gpl.append_child(do_fill || cyclic ? "polygon" : "polyline");
+
+ color_string_set(gpl, gps, node_gps, do_fill);
+
+ if (is_stroke && !do_fill) {
+ node_gps.append_attribute("stroke-width").set_value((radius * 2.0f) - gpl->line_change);
+ }
+
+ std::string txt;
+ for (const int i : IndexRange(gps->totpoints)) {
+ if (i > 0) {
+ txt.append(" ");
+ }
+ bGPDspoint *pt = &gps->points[i];
+ const float2 screen_co = gpencil_3D_point_to_2D(&pt->x);
+ txt.append(std::to_string(screen_co.x) + "," + std::to_string(screen_co.y));
+ }
+
+ node_gps.append_attribute("points").set_value(txt.c_str());
+}
+
+/**
+ * Set color SVG string for stroke
+ * \param node_gps: Stroke node
+ * @param do_fill: True if the stroke is only fill
+ */
+void GpencilExporterSVG::color_string_set(bGPDlayer *gpl,
+ bGPDstroke *gps,
+ pugi::xml_node node_gps,
+ const bool do_fill)
+{
+ const bool round_cap = (gps->caps[0] == GP_STROKE_CAP_ROUND ||
+ gps->caps[1] == GP_STROKE_CAP_ROUND);
+
+ float col[3];
+ if (do_fill) {
+ interp_v3_v3v3(col, fill_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ std::string stroke_hex = rgb_to_hexstr(col);
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke").set_value("none");
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+ }
+ else {
+ interp_v3_v3v3(col, stroke_color_, gpl->tintcolor, gpl->tintcolor[3]);
+ linearrgb_to_srgb_v3_v3(col, col);
+ std::string stroke_hex = rgb_to_hexstr(col);
+ node_gps.append_attribute("stroke").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("stroke-opacity")
+ .set_value(stroke_color_[3] * stroke_average_opacity_get() * gpl->opacity);
+
+ if (gps->totpoints > 1) {
+ node_gps.append_attribute("fill").set_value("none");
+ node_gps.append_attribute("stroke-linecap").set_value(round_cap ? "round" : "square");
+ }
+ else {
+ node_gps.append_attribute("fill").set_value(stroke_hex.c_str());
+ node_gps.append_attribute("fill-opacity").set_value(fill_color_[3] * gpl->opacity);
+ }
+ }
+}
+
+/**
+ * Create a SVG rectangle
+ * \param node: Parent node
+ * \param x: X location
+ * \param y: Y location
+ * \param width: width of the recntagle
+ * \param height: Height of the rectangle
+ * \param thickness: Thickness of the line
+ * \param hexcolor: Color of the line
+ */
+void GpencilExporterSVG::add_rect(pugi::xml_node node,
+ float x,
+ float y,
+ float width,
+ float height,
+ float thickness,
+ std::string hexcolor)
+{
+ pugi::xml_node rect_node = node.append_child("rect");
+ rect_node.append_attribute("x").set_value(x);
+ rect_node.append_attribute("y").set_value(y);
+ rect_node.append_attribute("width").set_value(width);
+ rect_node.append_attribute("height").set_value(height);
+ rect_node.append_attribute("fill").set_value("none");
+ if (thickness > 0.0f) {
+ rect_node.append_attribute("stroke").set_value(hexcolor.c_str());
+ rect_node.append_attribute("stroke-width").set_value(thickness);
+ }
+}
+
+/**
+ * Create SVG text
+ * \param node: Parent node
+ * \param x: X location
+ * \param y: Y location
+ * \param text: Text to include
+ * \param size: Size of th etext
+ * \param hexcolor: Color of the text
+ */
+void GpencilExporterSVG::add_text(pugi::xml_node node,
+ float x,
+ float y,
+ std::string text,
+ const float size,
+ std::string hexcolor)
+{
+ pugi::xml_node nodetxt = node.append_child("text");
+
+ nodetxt.append_attribute("x").set_value(x);
+ nodetxt.append_attribute("y").set_value(y);
+ // nodetxt.append_attribute("font-family").set_value("'system-ui'");
+ nodetxt.append_attribute("font-size").set_value(size);
+ nodetxt.append_attribute("fill").set_value(hexcolor.c_str());
+ nodetxt.text().set(text.c_str());
+}
+
+/** Convert a color to Hex value (#FFFFFF). */
+std::string GpencilExporterSVG::rgb_to_hexstr(float color[3])
+{
+ uint8_t r = color[0] * 255.0f;
+ uint8_t g = color[1] * 255.0f;
+ uint8_t b = color[2] * 255.0f;
+ char hex_string[20];
+ sprintf(hex_string, "#%02X%02X%02X", r, g, b);
+
+ std::string hexstr = hex_string;
+
+ return hexstr;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_export_svg.h b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h
new file mode 100644
index 00000000000..f564736c16e
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_export_svg.h
@@ -0,0 +1,89 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "BLI_path_util.h"
+
+#include "gpencil_io_export_base.h"
+#include "pugixml.hpp"
+
+struct GpencilIOParams;
+
+#define SVG_EXPORTER_NAME "SVG Export for Grease Pencil"
+#define SVG_EXPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilExporterSVG : public GpencilExporter {
+
+ public:
+ GpencilExporterSVG(const char *filename, const struct GpencilIOParams *iparams);
+ bool add_newpage();
+ bool add_body();
+ bool write();
+
+ protected:
+ static void add_rect(pugi::xml_node node,
+ float x,
+ float y,
+ float width,
+ float height,
+ float thickness,
+ std::string hexcolor);
+
+ static void add_text(pugi::xml_node node,
+ float x,
+ float y,
+ std::string text,
+ const float size,
+ std::string hexcolor);
+
+ private:
+ /* XML doc. */
+ pugi::xml_document main_doc_;
+ /* Main document node. */
+ pugi::xml_node main_node_;
+ /** Frame node */
+ pugi::xml_node frame_node_;
+ void create_document_header();
+ void export_gpencil_layers();
+
+ void export_stroke_to_path(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_fill);
+
+ void export_stroke_to_polyline(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gpl,
+ const bool is_stroke,
+ const bool is_fill);
+
+ void color_string_set(struct bGPDlayer *gpl,
+ struct bGPDstroke *gps,
+ pugi::xml_node node_gps,
+ const bool is_fill);
+
+ std::string rgb_to_hexstr(float color[3]);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.cc b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc
new file mode 100644
index 00000000000..b49b4c969ad
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.cc
@@ -0,0 +1,85 @@
+
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "BLI_math_vector.h"
+
+#include "DNA_material_types.h"
+#include "DNA_object_types.h"
+#include "DNA_scene_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_material.h"
+
+#include "ED_gpencil.h"
+
+#include "gpencil_io_import_base.h"
+
+#include "pugixml.hpp"
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilImporter::GpencilImporter(const GpencilIOParams *iparams) : GpencilIO(iparams)
+{
+ /* Nothing to do yet */
+}
+
+Object *GpencilImporter::create_object()
+{
+ const float *cur = scene_->cursor.location;
+ ushort local_view_bits = (params_.v3d && params_.v3d->localvd) ? params_.v3d->local_view_uuid :
+ (ushort)0;
+ Object *ob_gpencil = ED_gpencil_add_object(params_.C, cur, local_view_bits);
+
+ return ob_gpencil;
+}
+
+int32_t GpencilImporter::create_material(const char *name, const bool stroke, const bool fill)
+{
+ const float default_stroke_color[4] = {0.0f, 0.0f, 0.0f, 1.0f};
+ const float default_fill_color[4] = {0.5f, 0.5f, 0.5f, 1.0f};
+ int32_t mat_index = BKE_gpencil_material_find_index_by_name_prefix(params_.ob, name);
+ /* Stroke and Fill material. */
+ if (mat_index == -1) {
+ int32_t new_idx;
+ Material *mat_gp = BKE_gpencil_object_material_new(bmain_, params_.ob, name, &new_idx);
+ MaterialGPencilStyle *gp_style = mat_gp->gp_style;
+ gp_style->flag &= ~GP_MATERIAL_STROKE_SHOW;
+ gp_style->flag &= ~GP_MATERIAL_FILL_SHOW;
+
+ copy_v4_v4(gp_style->stroke_rgba, default_stroke_color);
+ copy_v4_v4(gp_style->fill_rgba, default_fill_color);
+ if (stroke) {
+ gp_style->flag |= GP_MATERIAL_STROKE_SHOW;
+ }
+ if (fill) {
+ gp_style->flag |= GP_MATERIAL_FILL_SHOW;
+ }
+ mat_index = params_.ob->totcol - 1;
+ }
+
+ return mat_index;
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_base.h b/source/blender/io/gpencil/intern/gpencil_io_import_base.h
new file mode 100644
index 00000000000..efe6264e4e9
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_base.h
@@ -0,0 +1,41 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_base.h"
+
+namespace blender::io::gpencil {
+
+class GpencilImporter : public GpencilIO {
+
+ public:
+ GpencilImporter(const struct GpencilIOParams *iparams);
+ virtual bool read() = 0;
+
+ protected:
+ struct Object *create_object();
+ int32_t create_material(const char *name, const bool stroke, const bool fill);
+
+ private:
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc
new file mode 100644
index 00000000000..7f450477ac2
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.cc
@@ -0,0 +1,253 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+
+/** \file
+ * \ingroup bgpencil
+ */
+
+#include "BLI_float3.hh"
+#include "BLI_math.h"
+#include "BLI_span.hh"
+
+#include "DNA_gpencil_types.h"
+
+#include "BKE_gpencil.h"
+#include "BKE_gpencil_geom.h"
+
+#include "DEG_depsgraph.h"
+#include "DEG_depsgraph_query.h"
+
+#include "ED_gpencil.h"
+
+#include "gpencil_io.h"
+#include "gpencil_io_import_svg.h"
+
+/* Custom flags for NanoSVG. */
+#define NANOSVG_ALL_COLOR_KEYWORDS
+#define NANOSVG_IMPLEMENTATION
+
+#include "nanosvg/nanosvg.h"
+
+using blender::MutableSpan;
+
+namespace blender::io::gpencil {
+
+/* Constructor. */
+GpencilImporterSVG::GpencilImporterSVG(const char *filename, const GpencilIOParams *iparams)
+ : GpencilImporter(iparams)
+{
+ filename_set(filename);
+}
+
+bool GpencilImporterSVG::read()
+{
+ bool result = true;
+ NSVGimage *svg_data = nullptr;
+ svg_data = nsvgParseFromFile(filename_, "mm", 96.0f);
+ if (svg_data == nullptr) {
+ std::cout << " Could not open SVG.\n ";
+ return false;
+ }
+
+ /* Create grease pencil object. */
+ params_.ob = create_object();
+ if (params_.ob == nullptr) {
+ std::cout << "Unable to create new object.\n";
+ if (svg_data) {
+ nsvgDelete(svg_data);
+ }
+
+ return false;
+ }
+ gpd_ = (bGPdata *)params_.ob->data;
+
+ /* Grease pencil is rotated 90 degrees in X axis by default. */
+ float matrix[4][4];
+ const float3 scale = float3(params_.scale);
+ unit_m4(matrix);
+ rotate_m4(matrix, 'X', DEG2RADF(-90.0f));
+ rescale_m4(matrix, scale);
+
+ /* Loop all shapes. */
+ char prv_id[70] = {"*"};
+ int prefix = 0;
+ for (NSVGshape *shape = svg_data->shapes; shape; shape = shape->next) {
+ char *layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
+ BLI_sprintfN("%s", shape->id_parent);
+ if (!STREQ(prv_id, layer_id)) {
+ prefix++;
+ MEM_freeN(layer_id);
+ layer_id = (shape->id_parent[0] == '\0') ? BLI_sprintfN("Layer_%03d", prefix) :
+ BLI_sprintfN("%s", shape->id_parent);
+ strcpy(prv_id, layer_id);
+ }
+
+ /* Check if the layer exist and create if needed. */
+ bGPDlayer *gpl = (bGPDlayer *)BLI_findstring(
+ &gpd_->layers, layer_id, offsetof(bGPDlayer, info));
+ if (gpl == nullptr) {
+ gpl = BKE_gpencil_layer_addnew(gpd_, layer_id, true);
+ /* Disable lights. */
+ gpl->flag &= ~GP_LAYER_USE_LIGHTS;
+ }
+ MEM_freeN(layer_id);
+
+ /* Check frame. */
+ bGPDframe *gpf = BKE_gpencil_layer_frame_get(gpl, cfra_, GP_GETFRAME_ADD_NEW);
+ /* Create materials. */
+ bool is_stroke = (bool)shape->stroke.type;
+ bool is_fill = (bool)shape->fill.type;
+ if ((!is_stroke) && (!is_fill)) {
+ is_stroke = true;
+ }
+
+ /* Create_shape materials. */
+ const char *const mat_names[] = {"Stroke", "Fill"};
+ int index = 0;
+ if ((is_stroke) && (is_fill)) {
+ index = 0;
+ is_fill = false;
+ }
+ else if ((!is_stroke) && (is_fill)) {
+ index = 1;
+ }
+ int32_t mat_index = create_material(mat_names[index], is_stroke, is_fill);
+
+ /* Loop all paths to create the stroke data. */
+ for (NSVGpath *path = shape->paths; path; path = path->next) {
+ create_stroke(gpd_, gpf, shape, path, mat_index, matrix);
+ }
+ }
+
+ /* Free SVG memory. */
+ nsvgDelete(svg_data);
+
+ /* Calculate bounding box and move all points to new origin center. */
+ float gp_center[3];
+ BKE_gpencil_centroid_3d(gpd_, gp_center);
+
+ LISTBASE_FOREACH (bGPDlayer *, gpl, &gpd_->layers) {
+ LISTBASE_FOREACH (bGPDframe *, gpf, &gpl->frames) {
+ LISTBASE_FOREACH (bGPDstroke *, gps, &gpf->strokes) {
+ for (bGPDspoint &pt : MutableSpan(gps->points, gps->totpoints)) {
+ sub_v3_v3(&pt.x, gp_center);
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+void GpencilImporterSVG::create_stroke(bGPdata *gpd,
+ bGPDframe *gpf,
+ NSVGshape *shape,
+ NSVGpath *path,
+ const int32_t mat_index,
+ const float matrix[4][4])
+{
+ const bool is_stroke = (bool)shape->stroke.type;
+ const bool is_fill = (bool)shape->fill.type;
+
+ const int edges = params_.resolution;
+ const float step = 1.0f / (float)(edges - 1);
+
+ const int totpoints = (path->npts / 3) * params_.resolution;
+
+ bGPDstroke *gps = BKE_gpencil_stroke_new(mat_index, totpoints, 1.0f);
+ BLI_addtail(&gpf->strokes, gps);
+
+ if (path->closed == '1') {
+ gps->flag |= GP_STROKE_CYCLIC;
+ }
+ if (is_stroke) {
+ gps->thickness = shape->strokeWidth * params_.scale;
+ }
+ /* Apply Fill vertex color. */
+ if (is_fill) {
+ NSVGpaint fill = shape->fill;
+ convert_color(fill.color, gps->vert_color_fill);
+ gps->fill_opacity_fac = gps->vert_color_fill[3];
+ gps->vert_color_fill[3] = 1.0f;
+ }
+
+ int start_index = 0;
+ for (int i = 0; i < path->npts - 1; i += 3) {
+ float *p = &path->pts[i * 2];
+ float a = 0.0f;
+ for (int v = 0; v < edges; v++) {
+ bGPDspoint *pt = &gps->points[start_index];
+ pt->strength = shape->opacity;
+ pt->pressure = 1.0f;
+ pt->z = 0.0f;
+ /* TODO: (antoniov) Can be improved loading curve data instead of loading strokes. */
+ interp_v2_v2v2v2v2_cubic(&pt->x, &p[0], &p[2], &p[4], &p[6], a);
+
+ /* Scale from milimeters. */
+ mul_v3_fl(&pt->x, 0.001f);
+ mul_m4_v3(matrix, &pt->x);
+
+ /* Apply color to vertex color. */
+ if (is_fill) {
+ NSVGpaint fill = shape->fill;
+ convert_color(fill.color, pt->vert_color);
+ }
+ if (is_stroke) {
+ NSVGpaint stroke = shape->stroke;
+ convert_color(stroke.color, pt->vert_color);
+ gps->fill_opacity_fac = pt->vert_color[3];
+ }
+ pt->vert_color[3] = 1.0f;
+
+ a += step;
+ start_index++;
+ }
+ }
+
+ /* Cleanup and recalculate geometry. */
+ BKE_gpencil_stroke_merge_distance(gpd, gpf, gps, 0.001f, true);
+ BKE_gpencil_stroke_geometry_update(gpd, gps);
+}
+
+/* Unpack internal NanoSVG color. */
+static void unpack_nano_color(const unsigned int pack, float r_col[4])
+{
+ unsigned char rgb_u[4];
+
+ rgb_u[0] = ((pack) >> 0) & 0xFF;
+ rgb_u[1] = ((pack) >> 8) & 0xFF;
+ rgb_u[2] = ((pack) >> 16) & 0xFF;
+ rgb_u[3] = ((pack) >> 24) & 0xFF;
+
+ r_col[0] = (float)rgb_u[0] / 255.0f;
+ r_col[1] = (float)rgb_u[1] / 255.0f;
+ r_col[2] = (float)rgb_u[2] / 255.0f;
+ r_col[3] = (float)rgb_u[3] / 255.0f;
+}
+
+void GpencilImporterSVG::convert_color(const int32_t color, float r_linear_rgba[4])
+{
+ float rgba[4];
+ unpack_nano_color(color, rgba);
+
+ srgb_to_linearrgb_v3_v3(r_linear_rgba, rgba);
+ r_linear_rgba[3] = rgba[3];
+}
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/intern/gpencil_io_import_svg.h b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h
new file mode 100644
index 00000000000..6a34ec8423b
--- /dev/null
+++ b/source/blender/io/gpencil/intern/gpencil_io_import_svg.h
@@ -0,0 +1,56 @@
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ * The Original Code is Copyright (C) 2020 Blender Foundation
+ * All rights reserved.
+ */
+#pragma once
+
+/** \file
+ * \ingroup bgpencil
+ */
+#include "gpencil_io_import_base.h"
+
+struct GpencilIOParams;
+struct NSVGshape;
+struct NSVGpath;
+struct bGPdata;
+struct bGPDframe;
+
+#define SVG_IMPORTER_NAME "SVG Import for Grease Pencil"
+#define SVG_IMPORTER_VERSION "v1.0"
+
+namespace blender::io::gpencil {
+
+class GpencilImporterSVG : public GpencilImporter {
+
+ public:
+ GpencilImporterSVG(const char *filename, const struct GpencilIOParams *iparams);
+
+ bool read();
+
+ protected:
+ private:
+ void create_stroke(struct bGPdata *gpd_,
+ struct bGPDframe *gpf,
+ struct NSVGshape *shape,
+ struct NSVGpath *path,
+ const int32_t mat_index,
+ const float matrix[4][4]);
+
+ void convert_color(const int32_t color, float r_linear_rgba[4]);
+};
+
+} // namespace blender::io::gpencil
diff --git a/source/blender/io/gpencil/nanosvg/nanosvg.h b/source/blender/io/gpencil/nanosvg/nanosvg.h
new file mode 100644
index 00000000000..1009d684f7c
--- /dev/null
+++ b/source/blender/io/gpencil/nanosvg/nanosvg.h
@@ -0,0 +1,3313 @@
+/*
+ * Copyright (c) 2013-14 Mikko Mononen memon@inside.org
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ *
+ * The SVG parser is based on Anti-Grain Geometry 2.4 SVG example
+ * Copyright (C) 2002-2004 Maxim Shemanarev (McSeem) (http://www.antigrain.com/)
+ *
+ * Arc calculation code based on canvg (https://code.google.com/p/canvg/)
+ *
+ * Bounding box calculation based on
+ * http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
+ *
+ * This is a modified version for Blender used by importers.
+ *
+ */
+
+#ifndef NANOSVG_H
+#define NANOSVG_H
+
+#ifndef NANOSVG_CPLUSPLUS
+# ifdef __cplusplus
+extern "C" {
+# endif
+#endif
+
+// NanoSVG is a simple stupid single-header-file SVG parse. The output of the parser is a list of
+// cubic bezier shapes.
+//
+// The library suits well for anything from rendering scalable icons in your editor application to
+// prototyping a game.
+//
+// NanoSVG supports a wide range of SVG features, but something may be missing, feel free to create
+// a pull request!
+//
+// The shapes in the SVG images are transformed by the viewBox and converted to specified units.
+// That is, you should get the same looking data as your designed in your favorite app.
+//
+// NanoSVG can return the paths in few different units. For example if you want to render an image,
+// you may choose to get the paths in pixels, or if you are feeding the data into a CNC-cutter, you
+// may want to use millimeters.
+//
+// The units passed to NanoSVG should be one of: 'px', 'pt', 'pc' 'mm', 'cm', or 'in'.
+// DPI (dots-per-inch) controls how the unit conversion is done.
+//
+// If you don't know or care about the units stuff, "px" and 96 should get you going.
+
+/* Example Usage:
+ // Load SVG
+ NSVGimage* image;
+ image = nsvgParseFromFile("test.svg", "px", 96);
+ printf("size: %f x %f\n", image->width, image->height);
+ // Use...
+ for (NSVGshape *shape = image->shapes; shape != NULL; shape = shape->next) {
+ for (NSVGpath *path = shape->paths; path != NULL; path = path->next) {
+ for (int i = 0; i < path->npts-1; i += 3) {
+ float* p = &path->pts[i*2];
+ drawCubicBez(p[0],p[1], p[2],p[3], p[4],p[5], p[6],p[7]);
+ }
+ }
+ }
+ // Delete
+ nsvgDelete(image);
+*/
+
+enum NSVGpaintType {
+ NSVG_PAINT_NONE = 0,
+ NSVG_PAINT_COLOR = 1,
+ NSVG_PAINT_LINEAR_GRADIENT = 2,
+ NSVG_PAINT_RADIAL_GRADIENT = 3
+};
+
+enum NSVGspreadType { NSVG_SPREAD_PAD = 0, NSVG_SPREAD_REFLECT = 1, NSVG_SPREAD_REPEAT = 2 };
+
+enum NSVGlineJoin { NSVG_JOIN_MITER = 0, NSVG_JOIN_ROUND = 1, NSVG_JOIN_BEVEL = 2 };
+
+enum NSVGlineCap { NSVG_CAP_BUTT = 0, NSVG_CAP_ROUND = 1, NSVG_CAP_SQUARE = 2 };
+
+enum NSVGfillRule { NSVG_FILLRULE_NONZERO = 0, NSVG_FILLRULE_EVENODD = 1 };
+
+enum NSVGflags { NSVG_FLAGS_VISIBLE = 0x01 };
+
+typedef struct NSVGgradientStop {
+ unsigned int color;
+ float offset;
+} NSVGgradientStop;
+
+typedef struct NSVGgradient {
+ float xform[6];
+ char spread;
+ float fx, fy;
+ int nstops;
+ NSVGgradientStop stops[1];
+} NSVGgradient;
+
+typedef struct NSVGpaint {
+ char type;
+ union {
+ unsigned int color;
+ NSVGgradient *gradient;
+ };
+} NSVGpaint;
+
+typedef struct NSVGpath {
+ float *pts; // Cubic bezier points: x0,y0, [cpx1,cpx1,cpx2,cpy2,x1,y1], ...
+ int npts; // Total number of bezier points.
+ char closed; // Flag indicating if shapes should be treated as closed.
+ float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy].
+ struct NSVGpath *next; // Pointer to next path, or NULL if last element.
+} NSVGpath;
+
+typedef struct NSVGshape {
+ char id[64]; // Optional 'id' attr of the shape or its group
+ /* Blender: Parent ID used for layer creation. */
+ char id_parent[64];
+ NSVGpaint fill; // Fill paint
+ NSVGpaint stroke; // Stroke paint
+ float opacity; // Opacity of the shape.
+ float strokeWidth; // Stroke width (scaled).
+ float strokeDashOffset; // Stroke dash offset (scaled).
+ float strokeDashArray[8]; // Stroke dash array (scaled).
+ char strokeDashCount; // Number of dash values in dash array.
+ char strokeLineJoin; // Stroke join type.
+ char strokeLineCap; // Stroke cap type.
+ float miterLimit; // Miter limit
+ char fillRule; // Fill rule, see NSVGfillRule.
+ unsigned char flags; // Logical or of NSVG_FLAGS_* flags
+ float bounds[4]; // Tight bounding box of the shape [minx,miny,maxx,maxy].
+ NSVGpath *paths; // Linked list of paths in the image.
+ struct NSVGshape *next; // Pointer to next shape, or NULL if last element.
+} NSVGshape;
+
+typedef struct NSVGimage {
+ float width; // Width of the image.
+ float height; // Height of the image.
+ NSVGshape *shapes; // Linked list of shapes in the image.
+} NSVGimage;
+
+// Parses SVG file from a file, returns SVG image as paths.
+NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi);
+
+// Parses SVG file from a null terminated string, returns SVG image as paths.
+// Important note: changes the string.
+NSVGimage *nsvgParse(char *input, const char *units, float dpi);
+
+// Duplicates a path.
+NSVGpath *nsvgDuplicatePath(NSVGpath *p);
+
+// Deletes an image.
+void nsvgDelete(NSVGimage *image);
+
+#ifndef NANOSVG_CPLUSPLUS
+# ifdef __cplusplus
+}
+# endif
+#endif
+
+#endif // NANOSVG_H
+
+#ifdef NANOSVG_IMPLEMENTATION
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define NSVG_PI (3.14159265358979323846264338327f)
+#define NSVG_KAPPA90 \
+ (0.5522847493f) // Length proportional to radius of a cubic bezier handle for 90deg arcs.
+
+#define NSVG_ALIGN_MIN 0
+#define NSVG_ALIGN_MID 1
+#define NSVG_ALIGN_MAX 2
+#define NSVG_ALIGN_NONE 0
+#define NSVG_ALIGN_MEET 1
+#define NSVG_ALIGN_SLICE 2
+
+#define NSVG_NOTUSED(v) \
+ do { \
+ (void)(1 ? (void)0 : ((void)(v))); \
+ } while (0)
+#define NSVG_RGB(r, g, b) (((unsigned int)r) | ((unsigned int)g << 8) | ((unsigned int)b << 16))
+
+#ifdef _MSC_VER
+# pragma warning(disable : 4996) // Switch off security warnings
+# pragma warning(disable : 4100) // Switch off unreferenced formal parameter warnings
+# ifdef __cplusplus
+# define NSVG_INLINE inline
+# else
+# define NSVG_INLINE
+# endif
+#else
+# define NSVG_INLINE inline
+#endif
+
+static int nsvg__isspace(char c)
+{
+ return strchr(" \t\n\v\f\r", c) != 0;
+}
+
+static int nsvg__isdigit(char c)
+{
+ return c >= '0' && c <= '9';
+}
+
+static NSVG_INLINE float nsvg__minf(float a, float b)
+{
+ return a < b ? a : b;
+}
+static NSVG_INLINE float nsvg__maxf(float a, float b)
+{
+ return a > b ? a : b;
+}
+
+// Simple XML parser
+
+#define NSVG_XML_TAG 1
+#define NSVG_XML_CONTENT 2
+#define NSVG_XML_MAX_ATTRIBS 256
+
+static void nsvg__parseContent(char *s, void (*contentCb)(void *ud, const char *s), void *ud)
+{
+ // Trim start white spaces
+ while (*s && nsvg__isspace(*s))
+ s++;
+ if (!*s)
+ return;
+
+ if (contentCb)
+ (*contentCb)(ud, s);
+}
+
+static void nsvg__parseElement(char *s,
+ void (*startelCb)(void *ud, const char *el, const char **attr),
+ void (*endelCb)(void *ud, const char *el),
+ void *ud)
+{
+ const char *attr[NSVG_XML_MAX_ATTRIBS];
+ int nattr = 0;
+ char *name;
+ int start = 0;
+ int end = 0;
+ char quote;
+
+ // Skip white space after the '<'
+ while (*s && nsvg__isspace(*s))
+ s++;
+
+ // Check if the tag is end tag
+ if (*s == '/') {
+ s++;
+ end = 1;
+ }
+ else {
+ start = 1;
+ }
+
+ // Skip comments, data and preprocessor stuff.
+ if (!*s || *s == '?' || *s == '!')
+ return;
+
+ // Get tag name
+ name = s;
+ while (*s && !nsvg__isspace(*s))
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+
+ // Get attribs
+ while (!end && *s && nattr < NSVG_XML_MAX_ATTRIBS - 3) {
+ char *name = NULL;
+ char *value = NULL;
+
+ // Skip white space before the attrib name
+ while (*s && nsvg__isspace(*s))
+ s++;
+ if (!*s)
+ break;
+ if (*s == '/') {
+ end = 1;
+ break;
+ }
+ name = s;
+ // Find end of the attrib name.
+ while (*s && !nsvg__isspace(*s) && *s != '=')
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+ // Skip until the beginning of the value.
+ while (*s && *s != '\"' && *s != '\'')
+ s++;
+ if (!*s)
+ break;
+ quote = *s;
+ s++;
+ // Store value and find the end of it.
+ value = s;
+ while (*s && *s != quote)
+ s++;
+ if (*s) {
+ *s++ = '\0';
+ }
+
+ // Store only well formed attributes
+ if (name && value) {
+ attr[nattr++] = name;
+ attr[nattr++] = value;
+ }
+ }
+
+ // List terminator
+ attr[nattr++] = 0;
+ attr[nattr++] = 0;
+
+ // Call callbacks.
+ if (start && startelCb)
+ (*startelCb)(ud, name, attr);
+ if (end && endelCb)
+ (*endelCb)(ud, name);
+}
+
+static int nsvg__parseXML(char *input,
+ void (*startelCb)(void *ud, const char *el, const char **attr),
+ void (*endelCb)(void *ud, const char *el),
+ void (*contentCb)(void *ud, const char *s),
+ void *ud)
+{
+ char *s = input;
+ char *mark = s;
+ int state = NSVG_XML_CONTENT;
+ while (*s) {
+ if (*s == '<' && state == NSVG_XML_CONTENT) {
+ // Start of a tag
+ *s++ = '\0';
+ nsvg__parseContent(mark, contentCb, ud);
+ mark = s;
+ state = NSVG_XML_TAG;
+ }
+ else if (*s == '>' && state == NSVG_XML_TAG) {
+ // Start of a content or new tag.
+ *s++ = '\0';
+ nsvg__parseElement(mark, startelCb, endelCb, ud);
+ mark = s;
+ state = NSVG_XML_CONTENT;
+ }
+ else {
+ s++;
+ }
+ }
+
+ return 1;
+}
+
+/* Simple SVG parser. */
+
+#define NSVG_MAX_ATTR 128
+#define NSVG_MAX_BREADCRUMB 5
+
+enum NSVGgradientUnits { NSVG_USER_SPACE = 0, NSVG_OBJECT_SPACE = 1 };
+
+#define NSVG_MAX_DASHES 8
+
+enum NSVGunits {
+ NSVG_UNITS_USER,
+ NSVG_UNITS_PX,
+ NSVG_UNITS_PT,
+ NSVG_UNITS_PC,
+ NSVG_UNITS_MM,
+ NSVG_UNITS_CM,
+ NSVG_UNITS_IN,
+ NSVG_UNITS_PERCENT,
+ NSVG_UNITS_EM,
+ NSVG_UNITS_EX
+};
+
+typedef struct NSVGcoordinate {
+ float value;
+ int units;
+} NSVGcoordinate;
+
+typedef struct NSVGlinearData {
+ NSVGcoordinate x1, y1, x2, y2;
+} NSVGlinearData;
+
+typedef struct NSVGradialData {
+ NSVGcoordinate cx, cy, r, fx, fy;
+} NSVGradialData;
+
+typedef struct NSVGgradientData {
+ char id[64];
+ char ref[64];
+ char type;
+ union {
+ NSVGlinearData linear;
+ NSVGradialData radial;
+ };
+ char spread;
+ char units;
+ float xform[6];
+ int nstops;
+ NSVGgradientStop *stops;
+ struct NSVGgradientData *next;
+} NSVGgradientData;
+
+typedef struct NSVGattrib {
+ char id[64];
+ float xform[6];
+ unsigned int fillColor;
+ unsigned int strokeColor;
+ float opacity;
+ float fillOpacity;
+ float strokeOpacity;
+ char fillGradient[64];
+ char strokeGradient[64];
+ float strokeWidth;
+ float strokeDashOffset;
+ float strokeDashArray[NSVG_MAX_DASHES];
+ int strokeDashCount;
+ char strokeLineJoin;
+ char strokeLineCap;
+ float miterLimit;
+ char fillRule;
+ float fontSize;
+ unsigned int stopColor;
+ float stopOpacity;
+ float stopOffset;
+ char hasFill;
+ char hasStroke;
+ char visible;
+} NSVGattrib;
+
+typedef struct NSVGparser {
+ NSVGattrib attr[NSVG_MAX_ATTR];
+ int attrHead;
+ float *pts;
+ int npts;
+ int cpts;
+ NSVGpath *plist;
+ NSVGimage *image;
+ NSVGgradientData *gradients;
+ NSVGshape *shapesTail;
+ float viewMinx, viewMiny, viewWidth, viewHeight;
+ int alignX, alignY, alignType;
+ float dpi;
+ char pathFlag;
+ char defsFlag;
+ /** Blender breadscrum for layers. */
+ char breadcrumb[NSVG_MAX_BREADCRUMB][64];
+ /** Blender number of elements in breadscrum. */
+ int breadcrumb_len;
+} NSVGparser;
+
+static void nsvg__xformIdentity(float *t)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetTranslation(float *t, float tx, float ty)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = tx;
+ t[5] = ty;
+}
+
+static void nsvg__xformSetScale(float *t, float sx, float sy)
+{
+ t[0] = sx;
+ t[1] = 0.0f;
+ t[2] = 0.0f;
+ t[3] = sy;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewX(float *t, float a)
+{
+ t[0] = 1.0f;
+ t[1] = 0.0f;
+ t[2] = tanf(a);
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetSkewY(float *t, float a)
+{
+ t[0] = 1.0f;
+ t[1] = tanf(a);
+ t[2] = 0.0f;
+ t[3] = 1.0f;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformSetRotation(float *t, float a)
+{
+ float cs = cosf(a), sn = sinf(a);
+ t[0] = cs;
+ t[1] = sn;
+ t[2] = -sn;
+ t[3] = cs;
+ t[4] = 0.0f;
+ t[5] = 0.0f;
+}
+
+static void nsvg__xformMultiply(float *t, float *s)
+{
+ float t0 = t[0] * s[0] + t[1] * s[2];
+ float t2 = t[2] * s[0] + t[3] * s[2];
+ float t4 = t[4] * s[0] + t[5] * s[2] + s[4];
+ t[1] = t[0] * s[1] + t[1] * s[3];
+ t[3] = t[2] * s[1] + t[3] * s[3];
+ t[5] = t[4] * s[1] + t[5] * s[3] + s[5];
+ t[0] = t0;
+ t[2] = t2;
+ t[4] = t4;
+}
+
+static void nsvg__xformInverse(float *inv, float *t)
+{
+ double invdet, det = (double)t[0] * t[3] - (double)t[2] * t[1];
+ if (det > -1e-6 && det < 1e-6) {
+ nsvg__xformIdentity(t);
+ return;
+ }
+ invdet = 1.0 / det;
+ inv[0] = (float)(t[3] * invdet);
+ inv[2] = (float)(-t[2] * invdet);
+ inv[4] = (float)(((double)t[2] * t[5] - (double)t[3] * t[4]) * invdet);
+ inv[1] = (float)(-t[1] * invdet);
+ inv[3] = (float)(t[0] * invdet);
+ inv[5] = (float)(((double)t[1] * t[4] - (double)t[0] * t[5]) * invdet);
+}
+
+static void nsvg__xformPremultiply(float *t, float *s)
+{
+ float s2[6];
+ memcpy(s2, s, sizeof(float) * 6);
+ nsvg__xformMultiply(s2, t);
+ memcpy(t, s2, sizeof(float) * 6);
+}
+
+static void nsvg__xformPoint(float *dx, float *dy, float x, float y, float *t)
+{
+ *dx = x * t[0] + y * t[2] + t[4];
+ *dy = x * t[1] + y * t[3] + t[5];
+}
+
+static void nsvg__xformVec(float *dx, float *dy, float x, float y, float *t)
+{
+ *dx = x * t[0] + y * t[2];
+ *dy = x * t[1] + y * t[3];
+}
+
+#define NSVG_EPSILON (1e-12)
+
+static int nsvg__ptInBounds(float *pt, float *bounds)
+{
+ return pt[0] >= bounds[0] && pt[0] <= bounds[2] && pt[1] >= bounds[1] && pt[1] <= bounds[3];
+}
+
+static double nsvg__evalBezier(double t, double p0, double p1, double p2, double p3)
+{
+ double it = 1.0 - t;
+ return it * it * it * p0 + 3.0 * it * it * t * p1 + 3.0 * it * t * t * p2 + t * t * t * p3;
+}
+
+static void nsvg__curveBounds(float *bounds, float *curve)
+{
+ int i, j, count;
+ double roots[2], a, b, c, b2ac, t, v;
+ float *v0 = &curve[0];
+ float *v1 = &curve[2];
+ float *v2 = &curve[4];
+ float *v3 = &curve[6];
+
+ // Start the bounding box by end points
+ bounds[0] = nsvg__minf(v0[0], v3[0]);
+ bounds[1] = nsvg__minf(v0[1], v3[1]);
+ bounds[2] = nsvg__maxf(v0[0], v3[0]);
+ bounds[3] = nsvg__maxf(v0[1], v3[1]);
+
+ // Bezier curve fits inside the convex hull of it's control points.
+ // If control points are inside the bounds, we're done.
+ if (nsvg__ptInBounds(v1, bounds) && nsvg__ptInBounds(v2, bounds))
+ return;
+
+ // Add bezier curve inflection points in X and Y.
+ for (i = 0; i < 2; i++) {
+ a = -3.0 * v0[i] + 9.0 * v1[i] - 9.0 * v2[i] + 3.0 * v3[i];
+ b = 6.0 * v0[i] - 12.0 * v1[i] + 6.0 * v2[i];
+ c = 3.0 * v1[i] - 3.0 * v0[i];
+ count = 0;
+ if (fabs(a) < NSVG_EPSILON) {
+ if (fabs(b) > NSVG_EPSILON) {
+ t = -c / b;
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ }
+ }
+ else {
+ b2ac = b * b - 4.0 * c * a;
+ if (b2ac > NSVG_EPSILON) {
+ t = (-b + sqrt(b2ac)) / (2.0 * a);
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ t = (-b - sqrt(b2ac)) / (2.0 * a);
+ if (t > NSVG_EPSILON && t < 1.0 - NSVG_EPSILON)
+ roots[count++] = t;
+ }
+ }
+ for (j = 0; j < count; j++) {
+ v = nsvg__evalBezier(roots[j], v0[i], v1[i], v2[i], v3[i]);
+ bounds[0 + i] = nsvg__minf(bounds[0 + i], (float)v);
+ bounds[2 + i] = nsvg__maxf(bounds[2 + i], (float)v);
+ }
+ }
+}
+
+static NSVGparser *nsvg__createParser()
+{
+ NSVGparser *p;
+ p = (NSVGparser *)malloc(sizeof(NSVGparser));
+ if (p == NULL)
+ goto error;
+ memset(p, 0, sizeof(NSVGparser));
+
+ p->image = (NSVGimage *)malloc(sizeof(NSVGimage));
+ if (p->image == NULL)
+ goto error;
+ memset(p->image, 0, sizeof(NSVGimage));
+
+ // Init style
+ nsvg__xformIdentity(p->attr[0].xform);
+ memset(p->attr[0].id, 0, sizeof p->attr[0].id);
+ p->attr[0].fillColor = NSVG_RGB(0, 0, 0);
+ p->attr[0].strokeColor = NSVG_RGB(0, 0, 0);
+ p->attr[0].opacity = 1;
+ p->attr[0].fillOpacity = 1;
+ p->attr[0].strokeOpacity = 1;
+ p->attr[0].stopOpacity = 1;
+ p->attr[0].strokeWidth = 1;
+ p->attr[0].strokeLineJoin = NSVG_JOIN_MITER;
+ p->attr[0].strokeLineCap = NSVG_CAP_BUTT;
+ p->attr[0].miterLimit = 4;
+ p->attr[0].fillRule = NSVG_FILLRULE_NONZERO;
+ p->attr[0].hasFill = 1;
+ p->attr[0].visible = 1;
+
+ return p;
+
+error:
+ if (p) {
+ if (p->image)
+ free(p->image);
+ free(p);
+ }
+ return NULL;
+}
+
+static void nsvg__deletePaths(NSVGpath *path)
+{
+ while (path) {
+ NSVGpath *next = path->next;
+ if (path->pts != NULL)
+ free(path->pts);
+ free(path);
+ path = next;
+ }
+}
+
+static void nsvg__deletePaint(NSVGpaint *paint)
+{
+ if (paint->type == NSVG_PAINT_LINEAR_GRADIENT || paint->type == NSVG_PAINT_RADIAL_GRADIENT)
+ free(paint->gradient);
+}
+
+static void nsvg__deleteGradientData(NSVGgradientData *grad)
+{
+ NSVGgradientData *next;
+ while (grad != NULL) {
+ next = grad->next;
+ free(grad->stops);
+ free(grad);
+ grad = next;
+ }
+}
+
+static void nsvg__deleteParser(NSVGparser *p)
+{
+ if (p != NULL) {
+ nsvg__deletePaths(p->plist);
+ nsvg__deleteGradientData(p->gradients);
+ nsvgDelete(p->image);
+ free(p->pts);
+ free(p);
+ }
+}
+
+static void nsvg__resetPath(NSVGparser *p)
+{
+ p->npts = 0;
+}
+
+static void nsvg__addPoint(NSVGparser *p, float x, float y)
+{
+ if (p->npts + 1 > p->cpts) {
+ p->cpts = p->cpts ? p->cpts * 2 : 8;
+ p->pts = (float *)realloc(p->pts, p->cpts * 2 * sizeof(float));
+ if (!p->pts)
+ return;
+ }
+ p->pts[p->npts * 2 + 0] = x;
+ p->pts[p->npts * 2 + 1] = y;
+ p->npts++;
+}
+
+static void nsvg__moveTo(NSVGparser *p, float x, float y)
+{
+ if (p->npts > 0) {
+ p->pts[(p->npts - 1) * 2 + 0] = x;
+ p->pts[(p->npts - 1) * 2 + 1] = y;
+ }
+ else {
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static void nsvg__lineTo(NSVGparser *p, float x, float y)
+{
+ float px, py, dx, dy;
+ if (p->npts > 0) {
+ px = p->pts[(p->npts - 1) * 2 + 0];
+ py = p->pts[(p->npts - 1) * 2 + 1];
+ dx = x - px;
+ dy = y - py;
+ nsvg__addPoint(p, px + dx / 3.0f, py + dy / 3.0f);
+ nsvg__addPoint(p, x - dx / 3.0f, y - dy / 3.0f);
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static void nsvg__cubicBezTo(
+ NSVGparser *p, float cpx1, float cpy1, float cpx2, float cpy2, float x, float y)
+{
+ if (p->npts > 0) {
+ nsvg__addPoint(p, cpx1, cpy1);
+ nsvg__addPoint(p, cpx2, cpy2);
+ nsvg__addPoint(p, x, y);
+ }
+}
+
+static NSVGattrib *nsvg__getAttr(NSVGparser *p)
+{
+ return &p->attr[p->attrHead];
+}
+
+static void nsvg__pushAttr(NSVGparser *p)
+{
+ if (p->attrHead < NSVG_MAX_ATTR - 1) {
+ p->attrHead++;
+ memcpy(&p->attr[p->attrHead], &p->attr[p->attrHead - 1], sizeof(NSVGattrib));
+ }
+}
+
+static void nsvg__popAttr(NSVGparser *p)
+{
+ if (p->attrHead > 0)
+ p->attrHead--;
+}
+
+static float nsvg__actualOrigX(NSVGparser *p)
+{
+ return p->viewMinx;
+}
+
+static float nsvg__actualOrigY(NSVGparser *p)
+{
+ return p->viewMiny;
+}
+
+static float nsvg__actualWidth(NSVGparser *p)
+{
+ return p->viewWidth;
+}
+
+static float nsvg__actualHeight(NSVGparser *p)
+{
+ return p->viewHeight;
+}
+
+static float nsvg__actualLength(NSVGparser *p)
+{
+ float w = nsvg__actualWidth(p), h = nsvg__actualHeight(p);
+ return sqrtf(w * w + h * h) / sqrtf(2.0f);
+}
+
+static float nsvg__convertToPixels(NSVGparser *p, NSVGcoordinate c, float orig, float length)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ switch (c.units) {
+ case NSVG_UNITS_USER:
+ return c.value;
+ case NSVG_UNITS_PX:
+ return c.value;
+ case NSVG_UNITS_PT:
+ return c.value / 72.0f * p->dpi;
+ case NSVG_UNITS_PC:
+ return c.value / 6.0f * p->dpi;
+ case NSVG_UNITS_MM:
+ return c.value / 25.4f * p->dpi;
+ case NSVG_UNITS_CM:
+ return c.value / 2.54f * p->dpi;
+ case NSVG_UNITS_IN:
+ return c.value * p->dpi;
+ case NSVG_UNITS_EM:
+ return c.value * attr->fontSize;
+ case NSVG_UNITS_EX:
+ return c.value * attr->fontSize * 0.52f; // x-height of Helvetica.
+ case NSVG_UNITS_PERCENT:
+ return orig + c.value / 100.0f * length;
+ default:
+ return c.value;
+ }
+ return c.value;
+}
+
+static NSVGgradientData *nsvg__findGradientData(NSVGparser *p, const char *id)
+{
+ NSVGgradientData *grad = p->gradients;
+ if (id == NULL || *id == '\0')
+ return NULL;
+ while (grad != NULL) {
+ if (strcmp(grad->id, id) == 0)
+ return grad;
+ grad = grad->next;
+ }
+ return NULL;
+}
+
+static NSVGgradient *nsvg__createGradient(NSVGparser *p,
+ const char *id,
+ const float *localBounds,
+ char *paintType)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ NSVGgradientData *data = NULL;
+ NSVGgradientData *ref = NULL;
+ NSVGgradientStop *stops = NULL;
+ NSVGgradient *grad;
+ float ox, oy, sw, sh, sl;
+ int nstops = 0;
+ int refIter;
+
+ data = nsvg__findGradientData(p, id);
+ if (data == NULL)
+ return NULL;
+
+ // TODO: use ref to fill in all unset values too.
+ ref = data;
+ refIter = 0;
+ while (ref != NULL) {
+ NSVGgradientData *nextRef = NULL;
+ if (stops == NULL && ref->stops != NULL) {
+ stops = ref->stops;
+ nstops = ref->nstops;
+ break;
+ }
+ nextRef = nsvg__findGradientData(p, ref->ref);
+ if (nextRef == ref)
+ break; // prevent infite loops on malformed data
+ ref = nextRef;
+ refIter++;
+ if (refIter > 32)
+ break; // prevent infite loops on malformed data
+ }
+ if (stops == NULL)
+ return NULL;
+
+ grad = (NSVGgradient *)malloc(sizeof(NSVGgradient) + sizeof(NSVGgradientStop) * (nstops - 1));
+ if (grad == NULL)
+ return NULL;
+
+ // The shape width and height.
+ if (data->units == NSVG_OBJECT_SPACE) {
+ ox = localBounds[0];
+ oy = localBounds[1];
+ sw = localBounds[2] - localBounds[0];
+ sh = localBounds[3] - localBounds[1];
+ }
+ else {
+ ox = nsvg__actualOrigX(p);
+ oy = nsvg__actualOrigY(p);
+ sw = nsvg__actualWidth(p);
+ sh = nsvg__actualHeight(p);
+ }
+ sl = sqrtf(sw * sw + sh * sh) / sqrtf(2.0f);
+
+ if (data->type == NSVG_PAINT_LINEAR_GRADIENT) {
+ float x1, y1, x2, y2, dx, dy;
+ x1 = nsvg__convertToPixels(p, data->linear.x1, ox, sw);
+ y1 = nsvg__convertToPixels(p, data->linear.y1, oy, sh);
+ x2 = nsvg__convertToPixels(p, data->linear.x2, ox, sw);
+ y2 = nsvg__convertToPixels(p, data->linear.y2, oy, sh);
+ // Calculate transform aligned to the line
+ dx = x2 - x1;
+ dy = y2 - y1;
+ grad->xform[0] = dy;
+ grad->xform[1] = -dx;
+ grad->xform[2] = dx;
+ grad->xform[3] = dy;
+ grad->xform[4] = x1;
+ grad->xform[5] = y1;
+ }
+ else {
+ float cx, cy, fx, fy, r;
+ cx = nsvg__convertToPixels(p, data->radial.cx, ox, sw);
+ cy = nsvg__convertToPixels(p, data->radial.cy, oy, sh);
+ fx = nsvg__convertToPixels(p, data->radial.fx, ox, sw);
+ fy = nsvg__convertToPixels(p, data->radial.fy, oy, sh);
+ r = nsvg__convertToPixels(p, data->radial.r, 0, sl);
+ // Calculate transform aligned to the circle
+ grad->xform[0] = r;
+ grad->xform[1] = 0;
+ grad->xform[2] = 0;
+ grad->xform[3] = r;
+ grad->xform[4] = cx;
+ grad->xform[5] = cy;
+ grad->fx = fx / r;
+ grad->fy = fy / r;
+ }
+
+ nsvg__xformMultiply(grad->xform, data->xform);
+ nsvg__xformMultiply(grad->xform, attr->xform);
+
+ grad->spread = data->spread;
+ memcpy(grad->stops, stops, nstops * sizeof(NSVGgradientStop));
+ grad->nstops = nstops;
+
+ *paintType = data->type;
+
+ return grad;
+}
+
+static float nsvg__getAverageScale(float *t)
+{
+ float sx = sqrtf(t[0] * t[0] + t[2] * t[2]);
+ float sy = sqrtf(t[1] * t[1] + t[3] * t[3]);
+ return (sx + sy) * 0.5f;
+}
+
+static void nsvg__getLocalBounds(float *bounds, NSVGshape *shape, float *xform)
+{
+ NSVGpath *path;
+ float curve[4 * 2], curveBounds[4];
+ int i, first = 1;
+ for (path = shape->paths; path != NULL; path = path->next) {
+ nsvg__xformPoint(&curve[0], &curve[1], path->pts[0], path->pts[1], xform);
+ for (i = 0; i < path->npts - 1; i += 3) {
+ nsvg__xformPoint(
+ &curve[2], &curve[3], path->pts[(i + 1) * 2], path->pts[(i + 1) * 2 + 1], xform);
+ nsvg__xformPoint(
+ &curve[4], &curve[5], path->pts[(i + 2) * 2], path->pts[(i + 2) * 2 + 1], xform);
+ nsvg__xformPoint(
+ &curve[6], &curve[7], path->pts[(i + 3) * 2], path->pts[(i + 3) * 2 + 1], xform);
+ nsvg__curveBounds(curveBounds, curve);
+ if (first) {
+ bounds[0] = curveBounds[0];
+ bounds[1] = curveBounds[1];
+ bounds[2] = curveBounds[2];
+ bounds[3] = curveBounds[3];
+ first = 0;
+ }
+ else {
+ bounds[0] = nsvg__minf(bounds[0], curveBounds[0]);
+ bounds[1] = nsvg__minf(bounds[1], curveBounds[1]);
+ bounds[2] = nsvg__maxf(bounds[2], curveBounds[2]);
+ bounds[3] = nsvg__maxf(bounds[3], curveBounds[3]);
+ }
+ curve[0] = curve[6];
+ curve[1] = curve[7];
+ }
+ }
+}
+
+static void nsvg__addShape(NSVGparser *p)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ float scale = 1.0f;
+ NSVGshape *shape;
+ NSVGpath *path;
+ int i;
+
+ if (p->plist == NULL)
+ return;
+
+ shape = (NSVGshape *)malloc(sizeof(NSVGshape));
+ if (shape == NULL)
+ goto error;
+ memset(shape, 0, sizeof(NSVGshape));
+
+ memcpy(shape->id, attr->id, sizeof shape->id);
+ /* Copy parent id from breadcrumb. */
+ if (p->breadcrumb_len > 0) {
+ memcpy(shape->id_parent, p->breadcrumb[0], sizeof shape->id_parent);
+ }
+ else {
+ memcpy(shape->id_parent, attr->id, sizeof shape->id_parent);
+ }
+
+ scale = nsvg__getAverageScale(attr->xform);
+ shape->strokeWidth = attr->strokeWidth * scale;
+ shape->strokeDashOffset = attr->strokeDashOffset * scale;
+ shape->strokeDashCount = (char)attr->strokeDashCount;
+ for (i = 0; i < attr->strokeDashCount; i++)
+ shape->strokeDashArray[i] = attr->strokeDashArray[i] * scale;
+ shape->strokeLineJoin = attr->strokeLineJoin;
+ shape->strokeLineCap = attr->strokeLineCap;
+ shape->miterLimit = attr->miterLimit;
+ shape->fillRule = attr->fillRule;
+ shape->opacity = attr->opacity;
+
+ shape->paths = p->plist;
+ p->plist = NULL;
+
+ // Calculate shape bounds
+ shape->bounds[0] = shape->paths->bounds[0];
+ shape->bounds[1] = shape->paths->bounds[1];
+ shape->bounds[2] = shape->paths->bounds[2];
+ shape->bounds[3] = shape->paths->bounds[3];
+ for (path = shape->paths->next; path != NULL; path = path->next) {
+ shape->bounds[0] = nsvg__minf(shape->bounds[0], path->bounds[0]);
+ shape->bounds[1] = nsvg__minf(shape->bounds[1], path->bounds[1]);
+ shape->bounds[2] = nsvg__maxf(shape->bounds[2], path->bounds[2]);
+ shape->bounds[3] = nsvg__maxf(shape->bounds[3], path->bounds[3]);
+ }
+
+ // Set fill
+ if (attr->hasFill == 0) {
+ shape->fill.type = NSVG_PAINT_NONE;
+ }
+ else if (attr->hasFill == 1) {
+ shape->fill.type = NSVG_PAINT_COLOR;
+ shape->fill.color = attr->fillColor;
+ shape->fill.color |= (unsigned int)(attr->fillOpacity * 255) << 24;
+ }
+ else if (attr->hasFill == 2) {
+ float inv[6], localBounds[4];
+ nsvg__xformInverse(inv, attr->xform);
+ nsvg__getLocalBounds(localBounds, shape, inv);
+ shape->fill.gradient = nsvg__createGradient(
+ p, attr->fillGradient, localBounds, &shape->fill.type);
+ if (shape->fill.gradient == NULL) {
+ shape->fill.type = NSVG_PAINT_NONE;
+ }
+ }
+
+ // Set stroke
+ if (attr->hasStroke == 0) {
+ shape->stroke.type = NSVG_PAINT_NONE;
+ }
+ else if (attr->hasStroke == 1) {
+ shape->stroke.type = NSVG_PAINT_COLOR;
+ shape->stroke.color = attr->strokeColor;
+ shape->stroke.color |= (unsigned int)(attr->strokeOpacity * 255) << 24;
+ }
+ else if (attr->hasStroke == 2) {
+ float inv[6], localBounds[4];
+ nsvg__xformInverse(inv, attr->xform);
+ nsvg__getLocalBounds(localBounds, shape, inv);
+ shape->stroke.gradient = nsvg__createGradient(
+ p, attr->strokeGradient, localBounds, &shape->stroke.type);
+ if (shape->stroke.gradient == NULL)
+ shape->stroke.type = NSVG_PAINT_NONE;
+ }
+
+ // Set flags
+ shape->flags = (attr->visible ? NSVG_FLAGS_VISIBLE : 0x00);
+
+ // Add to tail
+ if (p->image->shapes == NULL)
+ p->image->shapes = shape;
+ else
+ p->shapesTail->next = shape;
+ p->shapesTail = shape;
+
+ return;
+
+error:
+ if (shape)
+ free(shape);
+}
+
+static void nsvg__addPath(NSVGparser *p, char closed)
+{
+ NSVGattrib *attr = nsvg__getAttr(p);
+ NSVGpath *path = NULL;
+ float bounds[4];
+ float *curve;
+ int i;
+
+ if (p->npts < 4)
+ return;
+
+ if (closed)
+ nsvg__lineTo(p, p->pts[0], p->pts[1]);
+
+ // Expect 1 + N*3 points (N = number of cubic bezier segments).
+ if ((p->npts % 3) != 1)
+ return;
+
+ path = (NSVGpath *)malloc(sizeof(NSVGpath));
+ if (path == NULL)
+ goto error;
+ memset(path, 0, sizeof(NSVGpath));
+
+ path->pts = (float *)malloc(p->npts * 2 * sizeof(float));
+ if (path->pts == NULL)
+ goto error;
+ path->closed = closed;
+ path->npts = p->npts;
+
+ // Transform path.
+ for (i = 0; i < p->npts; ++i)
+ nsvg__xformPoint(
+ &path->pts[i * 2], &path->pts[i * 2 + 1], p->pts[i * 2], p->pts[i * 2 + 1], attr->xform);
+
+ // Find bounds
+ for (i = 0; i < path->npts - 1; i += 3) {
+ curve = &path->pts[i * 2];
+ nsvg__curveBounds(bounds, curve);
+ if (i == 0) {
+ path->bounds[0] = bounds[0];
+ path->bounds[1] = bounds[1];
+ path->bounds[2] = bounds[2];
+ path->bounds[3] = bounds[3];
+ }
+ else {
+ path->bounds[0] = nsvg__minf(path->bounds[0], bounds[0]);
+ path->bounds[1] = nsvg__minf(path->bounds[1], bounds[1]);
+ path->bounds[2] = nsvg__maxf(path->bounds[2], bounds[2]);
+ path->bounds[3] = nsvg__maxf(path->bounds[3], bounds[3]);
+ }
+ }
+
+ path->next = p->plist;
+ p->plist = path;
+
+ return;
+
+error:
+ if (path != NULL) {
+ if (path->pts != NULL)
+ free(path->pts);
+ free(path);
+ }
+}
+
+// We roll our own string to float because the std library one uses locale and messes things up.
+static double nsvg__atof(const char *s)
+{
+ char *cur = (char *)s;
+ char *end = NULL;
+ double res = 0.0, sign = 1.0;
+ long long intPart = 0, fracPart = 0;
+ char hasIntPart = 0, hasFracPart = 0;
+
+ // Parse optional sign
+ if (*cur == '+') {
+ cur++;
+ }
+ else if (*cur == '-') {
+ sign = -1;
+ cur++;
+ }
+
+ // Parse integer part
+ if (nsvg__isdigit(*cur)) {
+ // Parse digit sequence
+ intPart = strtoll(cur, &end, 10);
+ if (cur != end) {
+ res = (double)intPart;
+ hasIntPart = 1;
+ cur = end;
+ }
+ }
+
+ // Parse fractional part.
+ if (*cur == '.') {
+ cur++; // Skip '.'
+ if (nsvg__isdigit(*cur)) {
+ // Parse digit sequence
+ fracPart = strtoll(cur, &end, 10);
+ if (cur != end) {
+ res += (double)fracPart / pow(10.0, (double)(end - cur));
+ hasFracPart = 1;
+ cur = end;
+ }
+ }
+ }
+
+ // A valid number should have integer or fractional part.
+ if (!hasIntPart && !hasFracPart)
+ return 0.0;
+
+ // Parse optional exponent
+ if (*cur == 'e' || *cur == 'E') {
+ long expPart = 0;
+ cur++; // skip 'E'
+ expPart = strtol(cur, &end, 10); // Parse digit sequence with sign
+ if (cur != end) {
+ res *= pow(10.0, (double)expPart);
+ }
+ }
+
+ return res * sign;
+}
+
+static const char *nsvg__parseNumber(const char *s, char *it, const int size)
+{
+ const int last = size - 1;
+ int i = 0;
+
+ // sign
+ if (*s == '-' || *s == '+') {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ // integer part
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ if (*s == '.') {
+ // decimal point
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ // fraction part
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ }
+ // exponent
+ if ((*s == 'e' || *s == 'E') && (s[1] != 'm' && s[1] != 'x')) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ if (*s == '-' || *s == '+') {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ while (*s && nsvg__isdigit(*s)) {
+ if (i < last)
+ it[i++] = *s;
+ s++;
+ }
+ }
+ it[i] = '\0';
+
+ return s;
+}
+
+static const char *nsvg__getNextPathItem(const char *s, char *it)
+{
+ it[0] = '\0';
+ // Skip white spaces and commas
+ while (*s && (nsvg__isspace(*s) || *s == ','))
+ s++;
+ if (!*s)
+ return s;
+ if (*s == '-' || *s == '+' || *s == '.' || nsvg__isdigit(*s)) {
+ s = nsvg__parseNumber(s, it, 64);
+ }
+ else {
+ // Parse command
+ it[0] = *s++;
+ it[1] = '\0';
+ return s;
+ }
+
+ return s;
+}
+
+static unsigned int nsvg__parseColorHex(const char *str)
+{
+ unsigned int c = 0, r = 0, g = 0, b = 0;
+ int n = 0;
+ str++; // skip #
+ // Calculate number of characters.
+ while (str[n] && !nsvg__isspace(str[n]))
+ n++;
+ if (n == 6) {
+ sscanf(str, "%x", &c);
+ }
+ else if (n == 3) {
+ sscanf(str, "%x", &c);
+ c = (c & 0xf) | ((c & 0xf0) << 4) | ((c & 0xf00) << 8);
+ c |= c << 4;
+ }
+ r = (c >> 16) & 0xff;
+ g = (c >> 8) & 0xff;
+ b = c & 0xff;
+ return NSVG_RGB(r, g, b);
+}
+
+static unsigned int nsvg__parseColorRGB(const char *str)
+{
+ int r = -1, g = -1, b = -1;
+ char s1[32] = "", s2[32] = "";
+ sscanf(str + 4, "%d%[%%, \t]%d%[%%, \t]%d", &r, s1, &g, s2, &b);
+ if (strchr(s1, '%')) {
+ return NSVG_RGB((r * 255) / 100, (g * 255) / 100, (b * 255) / 100);
+ }
+ else {
+ return NSVG_RGB(r, g, b);
+ }
+}
+
+typedef struct NSVGNamedColor {
+ const char *name;
+ unsigned int color;
+} NSVGNamedColor;
+
+NSVGNamedColor nsvg__colors[] = {
+
+ {"red", NSVG_RGB(255, 0, 0)},
+ {"green", NSVG_RGB(0, 128, 0)},
+ {"blue", NSVG_RGB(0, 0, 255)},
+ {"yellow", NSVG_RGB(255, 255, 0)},
+ {"cyan", NSVG_RGB(0, 255, 255)},
+ {"magenta", NSVG_RGB(255, 0, 255)},
+ {"black", NSVG_RGB(0, 0, 0)},
+ {"grey", NSVG_RGB(128, 128, 128)},
+ {"gray", NSVG_RGB(128, 128, 128)},
+ {"white", NSVG_RGB(255, 255, 255)},
+
+#ifdef NANOSVG_ALL_COLOR_KEYWORDS
+ {"aliceblue", NSVG_RGB(240, 248, 255)},
+ {"antiquewhite", NSVG_RGB(250, 235, 215)},
+ {"aqua", NSVG_RGB(0, 255, 255)},
+ {"aquamarine", NSVG_RGB(127, 255, 212)},
+ {"azure", NSVG_RGB(240, 255, 255)},
+ {"beige", NSVG_RGB(245, 245, 220)},
+ {"bisque", NSVG_RGB(255, 228, 196)},
+ {"blanchedalmond", NSVG_RGB(255, 235, 205)},
+ {"blueviolet", NSVG_RGB(138, 43, 226)},
+ {"brown", NSVG_RGB(165, 42, 42)},
+ {"burlywood", NSVG_RGB(222, 184, 135)},
+ {"cadetblue", NSVG_RGB(95, 158, 160)},
+ {"chartreuse", NSVG_RGB(127, 255, 0)},
+ {"chocolate", NSVG_RGB(210, 105, 30)},
+ {"coral", NSVG_RGB(255, 127, 80)},
+ {"cornflowerblue", NSVG_RGB(100, 149, 237)},
+ {"cornsilk", NSVG_RGB(255, 248, 220)},
+ {"crimson", NSVG_RGB(220, 20, 60)},
+ {"darkblue", NSVG_RGB(0, 0, 139)},
+ {"darkcyan", NSVG_RGB(0, 139, 139)},
+ {"darkgoldenrod", NSVG_RGB(184, 134, 11)},
+ {"darkgray", NSVG_RGB(169, 169, 169)},
+ {"darkgreen", NSVG_RGB(0, 100, 0)},
+ {"darkgrey", NSVG_RGB(169, 169, 169)},
+ {"darkkhaki", NSVG_RGB(189, 183, 107)},
+ {"darkmagenta", NSVG_RGB(139, 0, 139)},
+ {"darkolivegreen", NSVG_RGB(85, 107, 47)},
+ {"darkorange", NSVG_RGB(255, 140, 0)},
+ {"darkorchid", NSVG_RGB(153, 50, 204)},
+ {"darkred", NSVG_RGB(139, 0, 0)},
+ {"darksalmon", NSVG_RGB(233, 150, 122)},
+ {"darkseagreen", NSVG_RGB(143, 188, 143)},
+ {"darkslateblue", NSVG_RGB(72, 61, 139)},
+ {"darkslategray", NSVG_RGB(47, 79, 79)},
+ {"darkslategrey", NSVG_RGB(47, 79, 79)},
+ {"darkturquoise", NSVG_RGB(0, 206, 209)},
+ {"darkviolet", NSVG_RGB(148, 0, 211)},
+ {"deeppink", NSVG_RGB(255, 20, 147)},
+ {"deepskyblue", NSVG_RGB(0, 191, 255)},
+ {"dimgray", NSVG_RGB(105, 105, 105)},
+ {"dimgrey", NSVG_RGB(105, 105, 105)},
+ {"dodgerblue", NSVG_RGB(30, 144, 255)},
+ {"firebrick", NSVG_RGB(178, 34, 34)},
+ {"floralwhite", NSVG_RGB(255, 250, 240)},
+ {"forestgreen", NSVG_RGB(34, 139, 34)},
+ {"fuchsia", NSVG_RGB(255, 0, 255)},
+ {"gainsboro", NSVG_RGB(220, 220, 220)},
+ {"ghostwhite", NSVG_RGB(248, 248, 255)},
+ {"gold", NSVG_RGB(255, 215, 0)},
+ {"goldenrod", NSVG_RGB(218, 165, 32)},
+ {"greenyellow", NSVG_RGB(173, 255, 47)},
+ {"honeydew", NSVG_RGB(240, 255, 240)},
+ {"hotpink", NSVG_RGB(255, 105, 180)},
+ {"indianred", NSVG_RGB(205, 92, 92)},
+ {"indigo", NSVG_RGB(75, 0, 130)},
+ {"ivory", NSVG_RGB(255, 255, 240)},
+ {"khaki", NSVG_RGB(240, 230, 140)},
+ {"lavender", NSVG_RGB(230, 230, 250)},
+ {"lavenderblush", NSVG_RGB(255, 240, 245)},
+ {"lawngreen", NSVG_RGB(124, 252, 0)},
+ {"lemonchiffon", NSVG_RGB(255, 250, 205)},
+ {"lightblue", NSVG_RGB(173, 216, 230)},
+ {"lightcoral", NSVG_RGB(240, 128, 128)},
+ {"lightcyan", NSVG_RGB(224, 255, 255)},
+ {"lightgoldenrodyellow", NSVG_RGB(250, 250, 210)},
+ {"lightgray", NSVG_RGB(211, 211, 211)},
+ {"lightgreen", NSVG_RGB(144, 238, 144)},
+ {"lightgrey", NSVG_RGB(211, 211, 211)},
+ {"lightpink", NSVG_RGB(255, 182, 193)},
+ {"lightsalmon", NSVG_RGB(255, 160, 122)},
+ {"lightseagreen", NSVG_RGB(32, 178, 170)},
+ {"lightskyblue", NSVG_RGB(135, 206, 250)},
+ {"lightslategray", NSVG_RGB(119, 136, 153)},
+ {"lightslategrey", NSVG_RGB(119, 136, 153)},
+ {"lightsteelblue", NSVG_RGB(176, 196, 222)},
+ {"lightyellow", NSVG_RGB(255, 255, 224)},
+ {"lime", NSVG_RGB(0, 255, 0)},
+ {"limegreen", NSVG_RGB(50, 205, 50)},
+ {"linen", NSVG_RGB(250, 240, 230)},
+ {"maroon", NSVG_RGB(128, 0, 0)},
+ {"mediumaquamarine", NSVG_RGB(102, 205, 170)},
+ {"mediumblue", NSVG_RGB(0, 0, 205)},
+ {"mediumorchid", NSVG_RGB(186, 85, 211)},
+ {"mediumpurple", NSVG_RGB(147, 112, 219)},
+ {"mediumseagreen", NSVG_RGB(60, 179, 113)},
+ {"mediumslateblue", NSVG_RGB(123, 104, 238)},
+ {"mediumspringgreen", NSVG_RGB(0, 250, 154)},
+ {"mediumturquoise", NSVG_RGB(72, 209, 204)},
+ {"mediumvioletred", NSVG_RGB(199, 21, 133)},
+ {"midnightblue", NSVG_RGB(25, 25, 112)},
+ {"mintcream", NSVG_RGB(245, 255, 250)},
+ {"mistyrose", NSVG_RGB(255, 228, 225)},
+ {"moccasin", NSVG_RGB(255, 228, 181)},
+ {"navajowhite", NSVG_RGB(255, 222, 173)},
+ {"navy", NSVG_RGB(0, 0, 128)},
+ {"oldlace", NSVG_RGB(253, 245, 230)},
+ {"olive", NSVG_RGB(128, 128, 0)},
+ {"olivedrab", NSVG_RGB(107, 142, 35)},
+ {"orange", NSVG_RGB(255, 165, 0)},
+ {"orangered", NSVG_RGB(255, 69, 0)},
+ {"orchid", NSVG_RGB(218, 112, 214)},
+ {"palegoldenrod", NSVG_RGB(238, 232, 170)},
+ {"palegreen", NSVG_RGB(152, 251, 152)},
+ {"paleturquoise", NSVG_RGB(175, 238, 238)},
+ {"palevioletred", NSVG_RGB(219, 112, 147)},
+ {"papayawhip", NSVG_RGB(255, 239, 213)},
+ {"peachpuff", NSVG_RGB(255, 218, 185)},
+ {"peru", NSVG_RGB(205, 133, 63)},
+ {"pink", NSVG_RGB(255, 192, 203)},
+ {"plum", NSVG_RGB(221, 160, 221)},
+ {"powderblue", NSVG_RGB(176, 224, 230)},
+ {"purple", NSVG_RGB(128, 0, 128)},
+ {"rosybrown", NSVG_RGB(188, 143, 143)},
+ {"royalblue", NSVG_RGB(65, 105, 225)},
+ {"saddlebrown", NSVG_RGB(139, 69, 19)},
+ {"salmon", NSVG_RGB(250, 128, 114)},
+ {"sandybrown", NSVG_RGB(244, 164, 96)},
+ {"seagreen", NSVG_RGB(46, 139, 87)},
+ {"seashell", NSVG_RGB(255, 245, 238)},
+ {"sienna", NSVG_RGB(160, 82, 45)},
+ {"silver", NSVG_RGB(192, 192, 192)},
+ {"skyblue", NSVG_RGB(135, 206, 235)},
+ {"slateblue", NSVG_RGB(106, 90, 205)},
+ {"slategray", NSVG_RGB(112, 128, 144)},
+ {"slategrey", NSVG_RGB(112, 128, 144)},
+ {"snow", NSVG_RGB(255, 250, 250)},
+ {"springgreen", NSVG_RGB(0, 255, 127)},
+ {"steelblue", NSVG_RGB(70, 130, 180)},
+ {"tan", NSVG_RGB(210, 180, 140)},
+ {"teal", NSVG_RGB(0, 128, 128)},
+ {"thistle", NSVG_RGB(216, 191, 216)},
+ {"tomato", NSVG_RGB(255, 99, 71)},
+ {"turquoise", NSVG_RGB(64, 224, 208)},
+ {"violet", NSVG_RGB(238, 130, 238)},
+ {"wheat", NSVG_RGB(245, 222, 179)},
+ {"whitesmoke", NSVG_RGB(245, 245, 245)},
+ {"yellowgreen", NSVG_RGB(154, 205, 50)},
+#endif
+};
+
+static unsigned int nsvg__parseColorName(const char *str)
+{
+ int i, ncolors = sizeof(nsvg__colors) / sizeof(NSVGNamedColor);
+
+ for (i = 0; i < ncolors; i++) {
+ if (strcmp(nsvg__colors[i].name, str) == 0) {
+ return nsvg__colors[i].color;
+ }
+ }
+
+ return NSVG_RGB(128, 128, 128);
+}
+
+static unsigned int nsvg__parseColor(const char *str)
+{
+ size_t len = 0;
+ while (*str == ' ')
+ ++str;
+ len = strlen(str);
+ if (len >= 1 && *str == '#')
+ return nsvg__parseColorHex(str);
+ else if (len >= 4 && str[0] == 'r' && str[1] == 'g' && str[2] == 'b' && str[3] == '(')
+ return nsvg__parseColorRGB(str);
+ return nsvg__parseColorName(str);
+}
+
+static float nsvg__parseOpacity(const char *str)
+{
+ float val = nsvg__atof(str);
+ if (val < 0.0f)
+ val = 0.0f;
+ if (val > 1.0f)
+ val = 1.0f;
+ return val;
+}
+
+static float nsvg__parseMiterLimit(const char *str)
+{
+ float val = nsvg__atof(str);
+ if (val < 0.0f)
+ val = 0.0f;
+ return val;
+}
+
+static int nsvg__parseUnits(const char *units)
+{
+ if (units[0] == 'p' && units[1] == 'x')
+ return NSVG_UNITS_PX;
+ else if (units[0] == 'p' && units[1] == 't')
+ return NSVG_UNITS_PT;
+ else if (units[0] == 'p' && units[1] == 'c')
+ return NSVG_UNITS_PC;
+ else if (units[0] == 'm' && units[1] == 'm')
+ return NSVG_UNITS_MM;
+ else if (units[0] == 'c' && units[1] == 'm')
+ return NSVG_UNITS_CM;
+ else if (units[0] == 'i' && units[1] == 'n')
+ return NSVG_UNITS_IN;
+ else if (units[0] == '%')
+ return NSVG_UNITS_PERCENT;
+ else if (units[0] == 'e' && units[1] == 'm')
+ return NSVG_UNITS_EM;
+ else if (units[0] == 'e' && units[1] == 'x')
+ return NSVG_UNITS_EX;
+ return NSVG_UNITS_USER;
+}
+
+static int nsvg__isCoordinate(const char *s)
+{
+ // optional sign
+ if (*s == '-' || *s == '+')
+ s++;
+ // must have at least one digit
+ return nsvg__isdigit(*s);
+}
+
+static NSVGcoordinate nsvg__parseCoordinateRaw(const char *str)
+{
+ NSVGcoordinate coord = {0, NSVG_UNITS_USER};
+ char buf[64];
+ coord.units = nsvg__parseUnits(nsvg__parseNumber(str, buf, 64));
+ coord.value = nsvg__atof(buf);
+ return coord;
+}
+
+static NSVGcoordinate nsvg__coord(float v, int units)
+{
+ NSVGcoordinate coord = {v, units};
+ return coord;
+}
+
+static float nsvg__parseCoordinate(NSVGparser *p, const char *str, float orig, float length)
+{
+ NSVGcoordinate coord = nsvg__parseCoordinateRaw(str);
+ return nsvg__convertToPixels(p, coord, orig, length);
+}
+
+static int nsvg__parseTransformArgs(const char *str, float *args, int maxNa, int *na)
+{
+ const char *end;
+ const char *ptr;
+ char it[64];
+
+ *na = 0;
+ ptr = str;
+ while (*ptr && *ptr != '(')
+ ++ptr;
+ if (*ptr == 0)
+ return 1;
+ end = ptr;
+ while (*end && *end != ')')
+ ++end;
+ if (*end == 0)
+ return 1;
+
+ while (ptr < end) {
+ if (*ptr == '-' || *ptr == '+' || *ptr == '.' || nsvg__isdigit(*ptr)) {
+ if (*na >= maxNa)
+ return 0;
+ ptr = nsvg__parseNumber(ptr, it, 64);
+ args[(*na)++] = (float)nsvg__atof(it);
+ }
+ else {
+ ++ptr;
+ }
+ }
+ return (int)(end - str);
+}
+
+static int nsvg__parseMatrix(float *xform, const char *str)
+{
+ float t[6];
+ int na = 0;
+ int len = nsvg__parseTransformArgs(str, t, 6, &na);
+ if (na != 6)
+ return len;
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseTranslate(float *xform, const char *str)
+{
+ float args[2];
+ float t[6];
+ int na = 0;
+ int len = nsvg__parseTransformArgs(str, args, 2, &na);
+ if (na == 1)
+ args[1] = 0.0;
+
+ nsvg__xformSetTranslation(t, args[0], args[1]);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseScale(float *xform, const char *str)
+{
+ float args[2];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 2, &na);
+ if (na == 1)
+ args[1] = args[0];
+ nsvg__xformSetScale(t, args[0], args[1]);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseSkewX(float *xform, const char *str)
+{
+ float args[1];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 1, &na);
+ nsvg__xformSetSkewX(t, args[0] / 180.0f * NSVG_PI);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseSkewY(float *xform, const char *str)
+{
+ float args[1];
+ int na = 0;
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 1, &na);
+ nsvg__xformSetSkewY(t, args[0] / 180.0f * NSVG_PI);
+ memcpy(xform, t, sizeof(float) * 6);
+ return len;
+}
+
+static int nsvg__parseRotate(float *xform, const char *str)
+{
+ float args[3];
+ int na = 0;
+ float m[6];
+ float t[6];
+ int len = nsvg__parseTransformArgs(str, args, 3, &na);
+ if (na == 1)
+ args[1] = args[2] = 0.0f;
+ nsvg__xformIdentity(m);
+
+ if (na > 1) {
+ nsvg__xformSetTranslation(t, -args[1], -args[2]);
+ nsvg__xformMultiply(m, t);
+ }
+
+ nsvg__xformSetRotation(t, args[0] / 180.0f * NSVG_PI);
+ nsvg__xformMultiply(m, t);
+
+ if (na > 1) {
+ nsvg__xformSetTranslation(t, args[1], args[2]);
+ nsvg__xformMultiply(m, t);
+ }
+
+ memcpy(xform, m, sizeof(float) * 6);
+
+ return len;
+}
+
+static void nsvg__parseTransform(float *xform, const char *str)
+{
+ float t[6];
+ int len;
+ nsvg__xformIdentity(xform);
+ while (*str) {
+ if (strncmp(str, "matrix", 6) == 0)
+ len = nsvg__parseMatrix(t, str);
+ else if (strncmp(str, "translate", 9) == 0)
+ len = nsvg__parseTranslate(t, str);
+ else if (strncmp(str, "scale", 5) == 0)
+ len = nsvg__parseScale(t, str);
+ else if (strncmp(str, "rotate", 6) == 0)
+ len = nsvg__parseRotate(t, str);
+ else if (strncmp(str, "skewX", 5) == 0)
+ len = nsvg__parseSkewX(t, str);
+ else if (strncmp(str, "skewY", 5) == 0)
+ len = nsvg__parseSkewY(t, str);
+ else {
+ ++str;
+ continue;
+ }
+ if (len != 0) {
+ str += len;
+ }
+ else {
+ ++str;
+ continue;
+ }
+
+ nsvg__xformPremultiply(xform, t);
+ }
+}
+
+static void nsvg__parseUrl(char *id, const char *str)
+{
+ int i = 0;
+ str += 4; // "url(";
+ if (*str == '#')
+ str++;
+ while (i < 63 && *str != ')') {
+ id[i] = *str++;
+ i++;
+ }
+ id[i] = '\0';
+}
+
+static char nsvg__parseLineCap(const char *str)
+{
+ if (strcmp(str, "butt") == 0)
+ return NSVG_CAP_BUTT;
+ else if (strcmp(str, "round") == 0)
+ return NSVG_CAP_ROUND;
+ else if (strcmp(str, "square") == 0)
+ return NSVG_CAP_SQUARE;
+ // TODO: handle inherit.
+ return NSVG_CAP_BUTT;
+}
+
+static char nsvg__parseLineJoin(const char *str)
+{
+ if (strcmp(str, "miter") == 0)
+ return NSVG_JOIN_MITER;
+ else if (strcmp(str, "round") == 0)
+ return NSVG_JOIN_ROUND;
+ else if (strcmp(str, "bevel") == 0)
+ return NSVG_JOIN_BEVEL;
+ // TODO: handle inherit.
+ return NSVG_JOIN_MITER;
+}
+
+static char nsvg__parseFillRule(const char *str)
+{
+ if (strcmp(str, "nonzero") == 0)
+ return NSVG_FILLRULE_NONZERO;
+ else if (strcmp(str, "evenodd") == 0)
+ return NSVG_FILLRULE_EVENODD;
+ // TODO: handle inherit.
+ return NSVG_FILLRULE_NONZERO;
+}
+
+static const char *nsvg__getNextDashItem(const char *s, char *it)
+{
+ int n = 0;
+ it[0] = '\0';
+ // Skip white spaces and commas
+ while (*s && (nsvg__isspace(*s) || *s == ','))
+ s++;
+ // Advance until whitespace, comma or end.
+ while (*s && (!nsvg__isspace(*s) && *s != ',')) {
+ if (n < 63)
+ it[n++] = *s;
+ s++;
+ }
+ it[n++] = '\0';
+ return s;
+}
+
+static int nsvg__parseStrokeDashArray(NSVGparser *p, const char *str, float *strokeDashArray)
+{
+ char item[64];
+ int count = 0, i;
+ float sum = 0.0f;
+
+ // Handle "none"
+ if (str[0] == 'n')
+ return 0;
+
+ // Parse dashes
+ while (*str) {
+ str = nsvg__getNextDashItem(str, item);
+ if (!*item)
+ break;
+ if (count < NSVG_MAX_DASHES)
+ strokeDashArray[count++] = fabsf(
+ nsvg__parseCoordinate(p, item, 0.0f, nsvg__actualLength(p)));
+ }
+
+ for (i = 0; i < count; i++)
+ sum += strokeDashArray[i];
+ if (sum <= 1e-6f)
+ count = 0;
+
+ return count;
+}
+
+static void nsvg__parseStyle(NSVGparser *p, const char *str);
+
+static int nsvg__parseAttr(NSVGparser *p, const char *name, const char *value)
+{
+ float xform[6];
+ NSVGattrib *attr = nsvg__getAttr(p);
+ if (!attr)
+ return 0;
+
+ if (strcmp(name, "style") == 0) {
+ nsvg__parseStyle(p, value);
+ }
+ else if (strcmp(name, "display") == 0) {
+ if (strcmp(value, "none") == 0)
+ attr->visible = 0;
+ // Don't reset ->visible on display:inline, one display:none hides the whole subtree
+ }
+ else if (strcmp(name, "fill") == 0) {
+ if (strcmp(value, "none") == 0) {
+ attr->hasFill = 0;
+ }
+ else if (strncmp(value, "url(", 4) == 0) {
+ attr->hasFill = 2;
+ nsvg__parseUrl(attr->fillGradient, value);
+ }
+ else {
+ attr->hasFill = 1;
+ attr->fillColor = nsvg__parseColor(value);
+ }
+ }
+ else if (strcmp(name, "opacity") == 0) {
+ attr->opacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "fill-opacity") == 0) {
+ attr->fillOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "stroke") == 0) {
+ if (strcmp(value, "none") == 0) {
+ attr->hasStroke = 0;
+ }
+ else if (strncmp(value, "url(", 4) == 0) {
+ attr->hasStroke = 2;
+ nsvg__parseUrl(attr->strokeGradient, value);
+ }
+ else {
+ attr->hasStroke = 1;
+ attr->strokeColor = nsvg__parseColor(value);
+ }
+ }
+ else if (strcmp(name, "stroke-width") == 0) {
+ attr->strokeWidth = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "stroke-dasharray") == 0) {
+ attr->strokeDashCount = nsvg__parseStrokeDashArray(p, value, attr->strokeDashArray);
+ }
+ else if (strcmp(name, "stroke-dashoffset") == 0) {
+ attr->strokeDashOffset = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "stroke-opacity") == 0) {
+ attr->strokeOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "stroke-linecap") == 0) {
+ attr->strokeLineCap = nsvg__parseLineCap(value);
+ }
+ else if (strcmp(name, "stroke-linejoin") == 0) {
+ attr->strokeLineJoin = nsvg__parseLineJoin(value);
+ }
+ else if (strcmp(name, "stroke-miterlimit") == 0) {
+ attr->miterLimit = nsvg__parseMiterLimit(value);
+ }
+ else if (strcmp(name, "fill-rule") == 0) {
+ attr->fillRule = nsvg__parseFillRule(value);
+ }
+ else if (strcmp(name, "font-size") == 0) {
+ attr->fontSize = nsvg__parseCoordinate(p, value, 0.0f, nsvg__actualLength(p));
+ }
+ else if (strcmp(name, "transform") == 0) {
+ nsvg__parseTransform(xform, value);
+ nsvg__xformPremultiply(attr->xform, xform);
+ }
+ else if (strcmp(name, "stop-color") == 0) {
+ attr->stopColor = nsvg__parseColor(value);
+ }
+ else if (strcmp(name, "stop-opacity") == 0) {
+ attr->stopOpacity = nsvg__parseOpacity(value);
+ }
+ else if (strcmp(name, "offset") == 0) {
+ attr->stopOffset = nsvg__parseCoordinate(p, value, 0.0f, 1.0f);
+ }
+ else if (strcmp(name, "id") == 0) {
+ strncpy(attr->id, value, 63);
+ attr->id[63] = '\0';
+ }
+ else {
+ return 0;
+ }
+ return 1;
+}
+
+static int nsvg__parseNameValue(NSVGparser *p, const char *start, const char *end)
+{
+ const char *str;
+ const char *val;
+ char name[512];
+ char value[512];
+ int n;
+
+ str = start;
+ while (str < end && *str != ':')
+ ++str;
+
+ val = str;
+
+ // Right Trim
+ while (str > start && (*str == ':' || nsvg__isspace(*str)))
+ --str;
+ ++str;
+
+ n = (int)(str - start);
+ if (n > 511)
+ n = 511;
+ if (n)
+ memcpy(name, start, n);
+ name[n] = 0;
+
+ while (val < end && (*val == ':' || nsvg__isspace(*val)))
+ ++val;
+
+ n = (int)(end - val);
+ if (n > 511)
+ n = 511;
+ if (n)
+ memcpy(value, val, n);
+ value[n] = 0;
+
+ return nsvg__parseAttr(p, name, value);
+}
+
+static void nsvg__parseStyle(NSVGparser *p, const char *str)
+{
+ const char *start;
+ const char *end;
+
+ while (*str) {
+ // Left Trim
+ while (*str && nsvg__isspace(*str))
+ ++str;
+ start = str;
+ while (*str && *str != ';')
+ ++str;
+ end = str;
+
+ // Right Trim
+ while (end > start && (*end == ';' || nsvg__isspace(*end)))
+ --end;
+ ++end;
+
+ nsvg__parseNameValue(p, start, end);
+ if (*str)
+ ++str;
+ }
+}
+
+static void nsvg__parseAttribs(NSVGparser *p, const char **attr)
+{
+ int i;
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "style") == 0)
+ nsvg__parseStyle(p, attr[i + 1]);
+ else
+ nsvg__parseAttr(p, attr[i], attr[i + 1]);
+ }
+}
+
+static int nsvg__getArgsPerElement(char cmd)
+{
+ switch (cmd) {
+ case 'v':
+ case 'V':
+ case 'h':
+ case 'H':
+ return 1;
+ case 'm':
+ case 'M':
+ case 'l':
+ case 'L':
+ case 't':
+ case 'T':
+ return 2;
+ case 'q':
+ case 'Q':
+ case 's':
+ case 'S':
+ return 4;
+ case 'c':
+ case 'C':
+ return 6;
+ case 'a':
+ case 'A':
+ return 7;
+ case 'z':
+ case 'Z':
+ return 0;
+ }
+ return -1;
+}
+
+static void nsvg__pathMoveTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel) {
+ *cpx += args[0];
+ *cpy += args[1];
+ }
+ else {
+ *cpx = args[0];
+ *cpy = args[1];
+ }
+ nsvg__moveTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel) {
+ *cpx += args[0];
+ *cpy += args[1];
+ }
+ else {
+ *cpx = args[0];
+ *cpy = args[1];
+ }
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathHLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel)
+ *cpx += args[0];
+ else
+ *cpx = args[0];
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathVLineTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ if (rel)
+ *cpy += args[0];
+ else
+ *cpy = args[0];
+ nsvg__lineTo(p, *cpx, *cpy);
+}
+
+static void nsvg__pathCubicBezTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x2, y2, cx1, cy1, cx2, cy2;
+
+ if (rel) {
+ cx1 = *cpx + args[0];
+ cy1 = *cpy + args[1];
+ cx2 = *cpx + args[2];
+ cy2 = *cpy + args[3];
+ x2 = *cpx + args[4];
+ y2 = *cpy + args[5];
+ }
+ else {
+ cx1 = args[0];
+ cy1 = args[1];
+ cx2 = args[2];
+ cy2 = args[3];
+ x2 = args[4];
+ y2 = args[5];
+ }
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx2;
+ *cpy2 = cy2;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathCubicBezShortTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ cx2 = *cpx + args[0];
+ cy2 = *cpy + args[1];
+ x2 = *cpx + args[2];
+ y2 = *cpy + args[3];
+ }
+ else {
+ cx2 = args[0];
+ cy2 = args[1];
+ x2 = args[2];
+ y2 = args[3];
+ }
+
+ cx1 = 2 * x1 - *cpx2;
+ cy1 = 2 * y1 - *cpy2;
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx2;
+ *cpy2 = cy2;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathQuadBezTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx, cy;
+ float cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ cx = *cpx + args[0];
+ cy = *cpy + args[1];
+ x2 = *cpx + args[2];
+ y2 = *cpy + args[3];
+ }
+ else {
+ cx = args[0];
+ cy = args[1];
+ x2 = args[2];
+ y2 = args[3];
+ }
+
+ // Convert to cubic bezier
+ cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+ cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+ cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+ cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx;
+ *cpy2 = cy;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__pathQuadBezShortTo(
+ NSVGparser *p, float *cpx, float *cpy, float *cpx2, float *cpy2, float *args, int rel)
+{
+ float x1, y1, x2, y2, cx, cy;
+ float cx1, cy1, cx2, cy2;
+
+ x1 = *cpx;
+ y1 = *cpy;
+ if (rel) {
+ x2 = *cpx + args[0];
+ y2 = *cpy + args[1];
+ }
+ else {
+ x2 = args[0];
+ y2 = args[1];
+ }
+
+ cx = 2 * x1 - *cpx2;
+ cy = 2 * y1 - *cpy2;
+
+ // Convert to cubix bezier
+ cx1 = x1 + 2.0f / 3.0f * (cx - x1);
+ cy1 = y1 + 2.0f / 3.0f * (cy - y1);
+ cx2 = x2 + 2.0f / 3.0f * (cx - x2);
+ cy2 = y2 + 2.0f / 3.0f * (cy - y2);
+
+ nsvg__cubicBezTo(p, cx1, cy1, cx2, cy2, x2, y2);
+
+ *cpx2 = cx;
+ *cpy2 = cy;
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static float nsvg__sqr(float x)
+{
+ return x * x;
+}
+static float nsvg__vmag(float x, float y)
+{
+ return sqrtf(x * x + y * y);
+}
+
+static float nsvg__vecrat(float ux, float uy, float vx, float vy)
+{
+ return (ux * vx + uy * vy) / (nsvg__vmag(ux, uy) * nsvg__vmag(vx, vy));
+}
+
+static float nsvg__vecang(float ux, float uy, float vx, float vy)
+{
+ float r = nsvg__vecrat(ux, uy, vx, vy);
+ if (r < -1.0f)
+ r = -1.0f;
+ if (r > 1.0f)
+ r = 1.0f;
+ return ((ux * vy < uy * vx) ? -1.0f : 1.0f) * acosf(r);
+}
+
+static void nsvg__pathArcTo(NSVGparser *p, float *cpx, float *cpy, float *args, int rel)
+{
+ // Ported from canvg (https://code.google.com/p/canvg/)
+ float rx, ry, rotx;
+ float x1, y1, x2, y2, cx, cy, dx, dy, d;
+ float x1p, y1p, cxp, cyp, s, sa, sb;
+ float ux, uy, vx, vy, a1, da;
+ float x, y, tanx, tany, a, px = 0, py = 0, ptanx = 0, ptany = 0, t[6];
+ float sinrx, cosrx;
+ int fa, fs;
+ int i, ndivs;
+ float hda, kappa;
+
+ rx = fabsf(args[0]); // y radius
+ ry = fabsf(args[1]); // x radius
+ rotx = args[2] / 180.0f * NSVG_PI; // x rotation angle
+ fa = fabsf(args[3]) > 1e-6 ? 1 : 0; // Large arc
+ fs = fabsf(args[4]) > 1e-6 ? 1 : 0; // Sweep direction
+ x1 = *cpx; // start point
+ y1 = *cpy;
+ if (rel) { // end point
+ x2 = *cpx + args[5];
+ y2 = *cpy + args[6];
+ }
+ else {
+ x2 = args[5];
+ y2 = args[6];
+ }
+
+ dx = x1 - x2;
+ dy = y1 - y2;
+ d = sqrtf(dx * dx + dy * dy);
+ if (d < 1e-6f || rx < 1e-6f || ry < 1e-6f) {
+ // The arc degenerates to a line
+ nsvg__lineTo(p, x2, y2);
+ *cpx = x2;
+ *cpy = y2;
+ return;
+ }
+
+ sinrx = sinf(rotx);
+ cosrx = cosf(rotx);
+
+ // Convert to center point parameterization.
+ // http://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes
+ // 1) Compute x1', y1'
+ x1p = cosrx * dx / 2.0f + sinrx * dy / 2.0f;
+ y1p = -sinrx * dx / 2.0f + cosrx * dy / 2.0f;
+ d = nsvg__sqr(x1p) / nsvg__sqr(rx) + nsvg__sqr(y1p) / nsvg__sqr(ry);
+ if (d > 1) {
+ d = sqrtf(d);
+ rx *= d;
+ ry *= d;
+ }
+ // 2) Compute cx', cy'
+ s = 0.0f;
+ sa = nsvg__sqr(rx) * nsvg__sqr(ry) - nsvg__sqr(rx) * nsvg__sqr(y1p) -
+ nsvg__sqr(ry) * nsvg__sqr(x1p);
+ sb = nsvg__sqr(rx) * nsvg__sqr(y1p) + nsvg__sqr(ry) * nsvg__sqr(x1p);
+ if (sa < 0.0f)
+ sa = 0.0f;
+ if (sb > 0.0f)
+ s = sqrtf(sa / sb);
+ if (fa == fs)
+ s = -s;
+ cxp = s * rx * y1p / ry;
+ cyp = s * -ry * x1p / rx;
+
+ // 3) Compute cx,cy from cx',cy'
+ cx = (x1 + x2) / 2.0f + cosrx * cxp - sinrx * cyp;
+ cy = (y1 + y2) / 2.0f + sinrx * cxp + cosrx * cyp;
+
+ // 4) Calculate theta1, and delta theta.
+ ux = (x1p - cxp) / rx;
+ uy = (y1p - cyp) / ry;
+ vx = (-x1p - cxp) / rx;
+ vy = (-y1p - cyp) / ry;
+ a1 = nsvg__vecang(1.0f, 0.0f, ux, uy); // Initial angle
+ da = nsvg__vecang(ux, uy, vx, vy); // Delta angle
+
+ // if (vecrat(ux,uy,vx,vy) <= -1.0f) da = NSVG_PI;
+ // if (vecrat(ux,uy,vx,vy) >= 1.0f) da = 0;
+
+ if (fs == 0 && da > 0)
+ da -= 2 * NSVG_PI;
+ else if (fs == 1 && da < 0)
+ da += 2 * NSVG_PI;
+
+ // Approximate the arc using cubic spline segments.
+ t[0] = cosrx;
+ t[1] = sinrx;
+ t[2] = -sinrx;
+ t[3] = cosrx;
+ t[4] = cx;
+ t[5] = cy;
+
+ // Split arc into max 90 degree segments.
+ // The loop assumes an iteration per end point (including start and end), this +1.
+ ndivs = (int)(fabsf(da) / (NSVG_PI * 0.5f) + 1.0f);
+ hda = (da / (float)ndivs) / 2.0f;
+ kappa = fabsf(4.0f / 3.0f * (1.0f - cosf(hda)) / sinf(hda));
+ if (da < 0.0f)
+ kappa = -kappa;
+
+ for (i = 0; i <= ndivs; i++) {
+ a = a1 + da * ((float)i / (float)ndivs);
+ dx = cosf(a);
+ dy = sinf(a);
+ nsvg__xformPoint(&x, &y, dx * rx, dy * ry, t); // position
+ nsvg__xformVec(&tanx, &tany, -dy * rx * kappa, dx * ry * kappa, t); // tangent
+ if (i > 0)
+ nsvg__cubicBezTo(p, px + ptanx, py + ptany, x - tanx, y - tany, x, y);
+ px = x;
+ py = y;
+ ptanx = tanx;
+ ptany = tany;
+ }
+
+ *cpx = x2;
+ *cpy = y2;
+}
+
+static void nsvg__parsePath(NSVGparser *p, const char **attr)
+{
+ const char *s = NULL;
+ char cmd = '\0';
+ float args[10];
+ int nargs;
+ int rargs = 0;
+ char initPoint;
+ float cpx, cpy, cpx2, cpy2;
+ const char *tmp[4];
+ char closedFlag;
+ int i;
+ char item[64];
+
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "d") == 0) {
+ s = attr[i + 1];
+ }
+ else {
+ tmp[0] = attr[i];
+ tmp[1] = attr[i + 1];
+ tmp[2] = 0;
+ tmp[3] = 0;
+ nsvg__parseAttribs(p, tmp);
+ }
+ }
+
+ if (s) {
+ nsvg__resetPath(p);
+ cpx = 0;
+ cpy = 0;
+ cpx2 = 0;
+ cpy2 = 0;
+ initPoint = 0;
+ closedFlag = 0;
+ nargs = 0;
+
+ while (*s) {
+ s = nsvg__getNextPathItem(s, item);
+ if (!*item)
+ break;
+ if (cmd != '\0' && nsvg__isCoordinate(item)) {
+ if (nargs < 10)
+ args[nargs++] = (float)nsvg__atof(item);
+ if (nargs >= rargs) {
+ switch (cmd) {
+ case 'm':
+ case 'M':
+ nsvg__pathMoveTo(p, &cpx, &cpy, args, cmd == 'm' ? 1 : 0);
+ // Moveto can be followed by multiple coordinate pairs,
+ // which should be treated as linetos.
+ cmd = (cmd == 'm') ? 'l' : 'L';
+ rargs = nsvg__getArgsPerElement(cmd);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ initPoint = 1;
+ break;
+ case 'l':
+ case 'L':
+ nsvg__pathLineTo(p, &cpx, &cpy, args, cmd == 'l' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'H':
+ case 'h':
+ nsvg__pathHLineTo(p, &cpx, &cpy, args, cmd == 'h' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'V':
+ case 'v':
+ nsvg__pathVLineTo(p, &cpx, &cpy, args, cmd == 'v' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ case 'C':
+ case 'c':
+ nsvg__pathCubicBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'c' ? 1 : 0);
+ break;
+ case 'S':
+ case 's':
+ nsvg__pathCubicBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 's' ? 1 : 0);
+ break;
+ case 'Q':
+ case 'q':
+ nsvg__pathQuadBezTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 'q' ? 1 : 0);
+ break;
+ case 'T':
+ case 't':
+ nsvg__pathQuadBezShortTo(p, &cpx, &cpy, &cpx2, &cpy2, args, cmd == 't' ? 1 : 0);
+ break;
+ case 'A':
+ case 'a':
+ nsvg__pathArcTo(p, &cpx, &cpy, args, cmd == 'a' ? 1 : 0);
+ cpx2 = cpx;
+ cpy2 = cpy;
+ break;
+ default:
+ if (nargs >= 2) {
+ cpx = args[nargs - 2];
+ cpy = args[nargs - 1];
+ cpx2 = cpx;
+ cpy2 = cpy;
+ }
+ break;
+ }
+ nargs = 0;
+ }
+ }
+ else {
+ cmd = item[0];
+ if (cmd == 'M' || cmd == 'm') {
+ // Commit path.
+ if (p->npts > 0)
+ nsvg__addPath(p, closedFlag);
+ // Start new subpath.
+ nsvg__resetPath(p);
+ closedFlag = 0;
+ nargs = 0;
+ }
+ else if (initPoint == 0) {
+ // Do not allow other commands until initial point has been set (moveTo called once).
+ cmd = '\0';
+ }
+ if (cmd == 'Z' || cmd == 'z') {
+ closedFlag = 1;
+ // Commit path.
+ if (p->npts > 0) {
+ // Move current point to first point
+ cpx = p->pts[0];
+ cpy = p->pts[1];
+ cpx2 = cpx;
+ cpy2 = cpy;
+ nsvg__addPath(p, closedFlag);
+ }
+ // Start new subpath.
+ nsvg__resetPath(p);
+ nsvg__moveTo(p, cpx, cpy);
+ closedFlag = 0;
+ nargs = 0;
+ }
+ rargs = nsvg__getArgsPerElement(cmd);
+ if (rargs == -1) {
+ // Command not recognized
+ cmd = '\0';
+ rargs = 0;
+ }
+ }
+ }
+ // Commit path.
+ if (p->npts)
+ nsvg__addPath(p, closedFlag);
+ }
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parseRect(NSVGparser *p, const char **attr)
+{
+ float x = 0.0f;
+ float y = 0.0f;
+ float w = 0.0f;
+ float h = 0.0f;
+ float rx = -1.0f; // marks not set
+ float ry = -1.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "x") == 0)
+ x = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y") == 0)
+ y = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "width") == 0)
+ w = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p));
+ if (strcmp(attr[i], "height") == 0)
+ h = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p));
+ if (strcmp(attr[i], "rx") == 0)
+ rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)));
+ if (strcmp(attr[i], "ry") == 0)
+ ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)));
+ }
+ }
+
+ if (rx < 0.0f && ry > 0.0f)
+ rx = ry;
+ if (ry < 0.0f && rx > 0.0f)
+ ry = rx;
+ if (rx < 0.0f)
+ rx = 0.0f;
+ if (ry < 0.0f)
+ ry = 0.0f;
+ if (rx > w / 2.0f)
+ rx = w / 2.0f;
+ if (ry > h / 2.0f)
+ ry = h / 2.0f;
+
+ if (w != 0.0f && h != 0.0f) {
+ nsvg__resetPath(p);
+
+ if (rx < 0.00001f || ry < 0.0001f) {
+ nsvg__moveTo(p, x, y);
+ nsvg__lineTo(p, x + w, y);
+ nsvg__lineTo(p, x + w, y + h);
+ nsvg__lineTo(p, x, y + h);
+ }
+ else {
+ // Rounded rectangle
+ nsvg__moveTo(p, x + rx, y);
+ nsvg__lineTo(p, x + w - rx, y);
+ nsvg__cubicBezTo(p,
+ x + w - rx * (1 - NSVG_KAPPA90),
+ y,
+ x + w,
+ y + ry * (1 - NSVG_KAPPA90),
+ x + w,
+ y + ry);
+ nsvg__lineTo(p, x + w, y + h - ry);
+ nsvg__cubicBezTo(p,
+ x + w,
+ y + h - ry * (1 - NSVG_KAPPA90),
+ x + w - rx * (1 - NSVG_KAPPA90),
+ y + h,
+ x + w - rx,
+ y + h);
+ nsvg__lineTo(p, x + rx, y + h);
+ nsvg__cubicBezTo(p,
+ x + rx * (1 - NSVG_KAPPA90),
+ y + h,
+ x,
+ y + h - ry * (1 - NSVG_KAPPA90),
+ x,
+ y + h - ry);
+ nsvg__lineTo(p, x, y + ry);
+ nsvg__cubicBezTo(
+ p, x, y + ry * (1 - NSVG_KAPPA90), x + rx * (1 - NSVG_KAPPA90), y, x + rx, y);
+ }
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseCircle(NSVGparser *p, const char **attr)
+{
+ float cx = 0.0f;
+ float cy = 0.0f;
+ float r = 0.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "cx") == 0)
+ cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "cy") == 0)
+ cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "r") == 0)
+ r = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualLength(p)));
+ }
+ }
+
+ if (r > 0.0f) {
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, cx + r, cy);
+ nsvg__cubicBezTo(p, cx + r, cy + r * NSVG_KAPPA90, cx + r * NSVG_KAPPA90, cy + r, cx, cy + r);
+ nsvg__cubicBezTo(p, cx - r * NSVG_KAPPA90, cy + r, cx - r, cy + r * NSVG_KAPPA90, cx - r, cy);
+ nsvg__cubicBezTo(p, cx - r, cy - r * NSVG_KAPPA90, cx - r * NSVG_KAPPA90, cy - r, cx, cy - r);
+ nsvg__cubicBezTo(p, cx + r * NSVG_KAPPA90, cy - r, cx + r, cy - r * NSVG_KAPPA90, cx + r, cy);
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseEllipse(NSVGparser *p, const char **attr)
+{
+ float cx = 0.0f;
+ float cy = 0.0f;
+ float rx = 0.0f;
+ float ry = 0.0f;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "cx") == 0)
+ cx = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "cy") == 0)
+ cy = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "rx") == 0)
+ rx = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualWidth(p)));
+ if (strcmp(attr[i], "ry") == 0)
+ ry = fabsf(nsvg__parseCoordinate(p, attr[i + 1], 0.0f, nsvg__actualHeight(p)));
+ }
+ }
+
+ if (rx > 0.0f && ry > 0.0f) {
+
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, cx + rx, cy);
+ nsvg__cubicBezTo(
+ p, cx + rx, cy + ry * NSVG_KAPPA90, cx + rx * NSVG_KAPPA90, cy + ry, cx, cy + ry);
+ nsvg__cubicBezTo(
+ p, cx - rx * NSVG_KAPPA90, cy + ry, cx - rx, cy + ry * NSVG_KAPPA90, cx - rx, cy);
+ nsvg__cubicBezTo(
+ p, cx - rx, cy - ry * NSVG_KAPPA90, cx - rx * NSVG_KAPPA90, cy - ry, cx, cy - ry);
+ nsvg__cubicBezTo(
+ p, cx + rx * NSVG_KAPPA90, cy - ry, cx + rx, cy - ry * NSVG_KAPPA90, cx + rx, cy);
+
+ nsvg__addPath(p, 1);
+
+ nsvg__addShape(p);
+ }
+}
+
+static void nsvg__parseLine(NSVGparser *p, const char **attr)
+{
+ float x1 = 0.0;
+ float y1 = 0.0;
+ float x2 = 0.0;
+ float y2 = 0.0;
+ int i;
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "x1") == 0)
+ x1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y1") == 0)
+ y1 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ if (strcmp(attr[i], "x2") == 0)
+ x2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigX(p), nsvg__actualWidth(p));
+ if (strcmp(attr[i], "y2") == 0)
+ y2 = nsvg__parseCoordinate(p, attr[i + 1], nsvg__actualOrigY(p), nsvg__actualHeight(p));
+ }
+ }
+
+ nsvg__resetPath(p);
+
+ nsvg__moveTo(p, x1, y1);
+ nsvg__lineTo(p, x2, y2);
+
+ nsvg__addPath(p, 0);
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parsePoly(NSVGparser *p, const char **attr, int closeFlag)
+{
+ int i;
+ const char *s;
+ float args[2];
+ int nargs, npts = 0;
+ char item[64];
+
+ nsvg__resetPath(p);
+
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "points") == 0) {
+ s = attr[i + 1];
+ nargs = 0;
+ while (*s) {
+ s = nsvg__getNextPathItem(s, item);
+ args[nargs++] = (float)nsvg__atof(item);
+ if (nargs >= 2) {
+ if (npts == 0)
+ nsvg__moveTo(p, args[0], args[1]);
+ else
+ nsvg__lineTo(p, args[0], args[1]);
+ nargs = 0;
+ npts++;
+ }
+ }
+ }
+ }
+ }
+
+ nsvg__addPath(p, (char)closeFlag);
+
+ nsvg__addShape(p);
+}
+
+static void nsvg__parseSVG(NSVGparser *p, const char **attr)
+{
+ int i;
+ for (i = 0; attr[i]; i += 2) {
+ if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "width") == 0) {
+ p->image->width = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
+ }
+ else if (strcmp(attr[i], "height") == 0) {
+ p->image->height = nsvg__parseCoordinate(p, attr[i + 1], 0.0f, 0.0f);
+ }
+ else if (strcmp(attr[i], "viewBox") == 0) {
+ const char *s = attr[i + 1];
+ char buf[64];
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewMinx = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewMiny = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewWidth = nsvg__atof(buf);
+ while (*s && (nsvg__isspace(*s) || *s == '%' || *s == ','))
+ s++;
+ if (!*s)
+ return;
+ s = nsvg__parseNumber(s, buf, 64);
+ p->viewHeight = nsvg__atof(buf);
+ }
+ else if (strcmp(attr[i], "preserveAspectRatio") == 0) {
+ if (strstr(attr[i + 1], "none") != 0) {
+ // No uniform scaling
+ p->alignType = NSVG_ALIGN_NONE;
+ }
+ else {
+ // Parse X align
+ if (strstr(attr[i + 1], "xMin") != 0)
+ p->alignX = NSVG_ALIGN_MIN;
+ else if (strstr(attr[i + 1], "xMid") != 0)
+ p->alignX = NSVG_ALIGN_MID;
+ else if (strstr(attr[i + 1], "xMax") != 0)
+ p->alignX = NSVG_ALIGN_MAX;
+ // Parse X align
+ if (strstr(attr[i + 1], "yMin") != 0)
+ p->alignY = NSVG_ALIGN_MIN;
+ else if (strstr(attr[i + 1], "yMid") != 0)
+ p->alignY = NSVG_ALIGN_MID;
+ else if (strstr(attr[i + 1], "yMax") != 0)
+ p->alignY = NSVG_ALIGN_MAX;
+ // Parse meet/slice
+ p->alignType = NSVG_ALIGN_MEET;
+ if (strstr(attr[i + 1], "slice") != 0)
+ p->alignType = NSVG_ALIGN_SLICE;
+ }
+ }
+ }
+ }
+}
+
+static void nsvg__parseGradient(NSVGparser *p, const char **attr, char type)
+{
+ int i;
+ NSVGgradientData *grad = (NSVGgradientData *)malloc(sizeof(NSVGgradientData));
+ if (grad == NULL)
+ return;
+ memset(grad, 0, sizeof(NSVGgradientData));
+ grad->units = NSVG_OBJECT_SPACE;
+ grad->type = type;
+ if (grad->type == NSVG_PAINT_LINEAR_GRADIENT) {
+ grad->linear.x1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ grad->linear.y1 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ grad->linear.x2 = nsvg__coord(100.0f, NSVG_UNITS_PERCENT);
+ grad->linear.y2 = nsvg__coord(0.0f, NSVG_UNITS_PERCENT);
+ }
+ else if (grad->type == NSVG_PAINT_RADIAL_GRADIENT) {
+ grad->radial.cx = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ grad->radial.cy = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ grad->radial.r = nsvg__coord(50.0f, NSVG_UNITS_PERCENT);
+ }
+
+ nsvg__xformIdentity(grad->xform);
+
+ for (i = 0; attr[i]; i += 2) {
+ if (strcmp(attr[i], "id") == 0) {
+ strncpy(grad->id, attr[i + 1], 63);
+ grad->id[63] = '\0';
+ }
+ else if (!nsvg__parseAttr(p, attr[i], attr[i + 1])) {
+ if (strcmp(attr[i], "gradientUnits") == 0) {
+ if (strcmp(attr[i + 1], "objectBoundingBox") == 0)
+ grad->units = NSVG_OBJECT_SPACE;
+ else
+ grad->units = NSVG_USER_SPACE;
+ }
+ else if (strcmp(attr[i], "gradientTransform") == 0) {
+ nsvg__parseTransform(grad->xform, attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "cx") == 0) {
+ grad->radial.cx = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "cy") == 0) {
+ grad->radial.cy = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "r") == 0) {
+ grad->radial.r = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "fx") == 0) {
+ grad->radial.fx = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "fy") == 0) {
+ grad->radial.fy = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "x1") == 0) {
+ grad->linear.x1 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "y1") == 0) {
+ grad->linear.y1 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "x2") == 0) {
+ grad->linear.x2 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "y2") == 0) {
+ grad->linear.y2 = nsvg__parseCoordinateRaw(attr[i + 1]);
+ }
+ else if (strcmp(attr[i], "spreadMethod") == 0) {
+ if (strcmp(attr[i + 1], "pad") == 0)
+ grad->spread = NSVG_SPREAD_PAD;
+ else if (strcmp(attr[i + 1], "reflect") == 0)
+ grad->spread = NSVG_SPREAD_REFLECT;
+ else if (strcmp(attr[i + 1], "repeat") == 0)
+ grad->spread = NSVG_SPREAD_REPEAT;
+ }
+ else if (strcmp(attr[i], "xlink:href") == 0) {
+ const char *href = attr[i + 1];
+ strncpy(grad->ref, href + 1, 62);
+ grad->ref[62] = '\0';
+ }
+ }
+ }
+
+ grad->next = p->gradients;
+ p->gradients = grad;
+}
+
+static void nsvg__parseGradientStop(NSVGparser *p, const char **attr)
+{
+ NSVGattrib *curAttr = nsvg__getAttr(p);
+ NSVGgradientData *grad;
+ NSVGgradientStop *stop;
+ int i, idx;
+
+ curAttr->stopOffset = 0;
+ curAttr->stopColor = 0;
+ curAttr->stopOpacity = 1.0f;
+
+ for (i = 0; attr[i]; i += 2) {
+ nsvg__parseAttr(p, attr[i], attr[i + 1]);
+ }
+
+ // Add stop to the last gradient.
+ grad = p->gradients;
+ if (grad == NULL)
+ return;
+
+ grad->nstops++;
+ grad->stops = (NSVGgradientStop *)realloc(grad->stops, sizeof(NSVGgradientStop) * grad->nstops);
+ if (grad->stops == NULL)
+ return;
+
+ // Insert
+ idx = grad->nstops - 1;
+ for (i = 0; i < grad->nstops - 1; i++) {
+ if (curAttr->stopOffset < grad->stops[i].offset) {
+ idx = i;
+ break;
+ }
+ }
+ if (idx != grad->nstops - 1) {
+ for (i = grad->nstops - 1; i > idx; i--)
+ grad->stops[i] = grad->stops[i - 1];
+ }
+
+ stop = &grad->stops[idx];
+ stop->color = curAttr->stopColor;
+ stop->color |= (unsigned int)(curAttr->stopOpacity * 255) << 24;
+ stop->offset = curAttr->stopOffset;
+}
+
+static void nsvg__startElement(void *ud, const char *el, const char **attr)
+{
+ NSVGparser *p = (NSVGparser *)ud;
+
+ if (p->defsFlag) {
+ // Skip everything but gradients in defs
+ if (strcmp(el, "linearGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
+ }
+ else if (strcmp(el, "radialGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
+ }
+ else if (strcmp(el, "stop") == 0) {
+ nsvg__parseGradientStop(p, attr);
+ }
+ return;
+ }
+
+ if (strcmp(el, "g") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseAttribs(p, attr);
+
+ /* Save the breadcrumb of groups. */
+ if (p->breadcrumb_len < NSVG_MAX_BREADCRUMB) {
+ NSVGattrib *attr_id = nsvg__getAttr(p);
+ memcpy(
+ p->breadcrumb[p->breadcrumb_len], attr_id->id, sizeof(p->breadcrumb[p->breadcrumb_len]));
+ p->breadcrumb_len++;
+ }
+ }
+ else if (strcmp(el, "path") == 0) {
+ if (p->pathFlag) // Do not allow nested paths.
+ return;
+ nsvg__pushAttr(p);
+ nsvg__parsePath(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "rect") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseRect(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "circle") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseCircle(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "ellipse") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseEllipse(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "line") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parseLine(p, attr);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "polyline") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parsePoly(p, attr, 0);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "polygon") == 0) {
+ nsvg__pushAttr(p);
+ nsvg__parsePoly(p, attr, 1);
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "linearGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_LINEAR_GRADIENT);
+ }
+ else if (strcmp(el, "radialGradient") == 0) {
+ nsvg__parseGradient(p, attr, NSVG_PAINT_RADIAL_GRADIENT);
+ }
+ else if (strcmp(el, "stop") == 0) {
+ nsvg__parseGradientStop(p, attr);
+ }
+ else if (strcmp(el, "defs") == 0) {
+ p->defsFlag = 1;
+ }
+ else if (strcmp(el, "svg") == 0) {
+ nsvg__parseSVG(p, attr);
+ }
+}
+
+static void nsvg__endElement(void *ud, const char *el)
+{
+ NSVGparser *p = (NSVGparser *)ud;
+
+ if (strcmp(el, "g") == 0) {
+ /* Remove the breadcrumb level. */
+ if (p->breadcrumb_len > 0) {
+ p->breadcrumb[p->breadcrumb_len - 1][0] = '\0';
+ p->breadcrumb_len--;
+ }
+
+ nsvg__popAttr(p);
+ }
+ else if (strcmp(el, "path") == 0) {
+ p->pathFlag = 0;
+ }
+ else if (strcmp(el, "defs") == 0) {
+ p->defsFlag = 0;
+ }
+}
+
+static void nsvg__content(void *ud, const char *s)
+{
+ NSVG_NOTUSED(ud);
+ NSVG_NOTUSED(s);
+ // empty
+}
+
+static void nsvg__imageBounds(NSVGparser *p, float *bounds)
+{
+ NSVGshape *shape;
+ shape = p->image->shapes;
+ if (shape == NULL) {
+ bounds[0] = bounds[1] = bounds[2] = bounds[3] = 0.0;
+ return;
+ }
+ bounds[0] = shape->bounds[0];
+ bounds[1] = shape->bounds[1];
+ bounds[2] = shape->bounds[2];
+ bounds[3] = shape->bounds[3];
+ for (shape = shape->next; shape != NULL; shape = shape->next) {
+ bounds[0] = nsvg__minf(bounds[0], shape->bounds[0]);
+ bounds[1] = nsvg__minf(bounds[1], shape->bounds[1]);
+ bounds[2] = nsvg__maxf(bounds[2], shape->bounds[2]);
+ bounds[3] = nsvg__maxf(bounds[3], shape->bounds[3]);
+ }
+}
+
+static float nsvg__viewAlign(float content, float container, int type)
+{
+ if (type == NSVG_ALIGN_MIN)
+ return 0;
+ else if (type == NSVG_ALIGN_MAX)
+ return container - content;
+ // mid
+ return (container - content) * 0.5f;
+}
+
+static void nsvg__scaleGradient(NSVGgradient *grad, float tx, float ty, float sx, float sy)
+{
+ float t[6];
+ nsvg__xformSetTranslation(t, tx, ty);
+ nsvg__xformMultiply(grad->xform, t);
+
+ nsvg__xformSetScale(t, sx, sy);
+ nsvg__xformMultiply(grad->xform, t);
+}
+
+static void nsvg__scaleToViewbox(NSVGparser *p, const char *units)
+{
+ NSVGshape *shape;
+ NSVGpath *path;
+ float tx, ty, sx, sy, us, bounds[4], t[6], avgs;
+ int i;
+ float *pt;
+
+ // Guess image size if not set completely.
+ nsvg__imageBounds(p, bounds);
+
+ if (p->viewWidth == 0) {
+ if (p->image->width > 0) {
+ p->viewWidth = p->image->width;
+ }
+ else {
+ p->viewMinx = bounds[0];
+ p->viewWidth = bounds[2] - bounds[0];
+ }
+ }
+ if (p->viewHeight == 0) {
+ if (p->image->height > 0) {
+ p->viewHeight = p->image->height;
+ }
+ else {
+ p->viewMiny = bounds[1];
+ p->viewHeight = bounds[3] - bounds[1];
+ }
+ }
+ if (p->image->width == 0)
+ p->image->width = p->viewWidth;
+ if (p->image->height == 0)
+ p->image->height = p->viewHeight;
+
+ tx = -p->viewMinx;
+ ty = -p->viewMiny;
+ sx = p->viewWidth > 0 ? p->image->width / p->viewWidth : 0;
+ sy = p->viewHeight > 0 ? p->image->height / p->viewHeight : 0;
+ // Unit scaling
+ us = 1.0f / nsvg__convertToPixels(p, nsvg__coord(1.0f, nsvg__parseUnits(units)), 0.0f, 1.0f);
+
+ // Fix aspect ratio
+ if (p->alignType == NSVG_ALIGN_MEET) {
+ // fit whole image into viewbox
+ sx = sy = nsvg__minf(sx, sy);
+ tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx;
+ ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy;
+ }
+ else if (p->alignType == NSVG_ALIGN_SLICE) {
+ // fill whole viewbox with image
+ sx = sy = nsvg__maxf(sx, sy);
+ tx += nsvg__viewAlign(p->viewWidth * sx, p->image->width, p->alignX) / sx;
+ ty += nsvg__viewAlign(p->viewHeight * sy, p->image->height, p->alignY) / sy;
+ }
+
+ // Transform
+ sx *= us;
+ sy *= us;
+ avgs = (sx + sy) / 2.0f;
+ for (shape = p->image->shapes; shape != NULL; shape = shape->next) {
+ shape->bounds[0] = (shape->bounds[0] + tx) * sx;
+ shape->bounds[1] = (shape->bounds[1] + ty) * sy;
+ shape->bounds[2] = (shape->bounds[2] + tx) * sx;
+ shape->bounds[3] = (shape->bounds[3] + ty) * sy;
+ for (path = shape->paths; path != NULL; path = path->next) {
+ path->bounds[0] = (path->bounds[0] + tx) * sx;
+ path->bounds[1] = (path->bounds[1] + ty) * sy;
+ path->bounds[2] = (path->bounds[2] + tx) * sx;
+ path->bounds[3] = (path->bounds[3] + ty) * sy;
+ for (i = 0; i < path->npts; i++) {
+ pt = &path->pts[i * 2];
+ pt[0] = (pt[0] + tx) * sx;
+ pt[1] = (pt[1] + ty) * sy;
+ }
+ }
+
+ if (shape->fill.type == NSVG_PAINT_LINEAR_GRADIENT ||
+ shape->fill.type == NSVG_PAINT_RADIAL_GRADIENT) {
+ nsvg__scaleGradient(shape->fill.gradient, tx, ty, sx, sy);
+ memcpy(t, shape->fill.gradient->xform, sizeof(float) * 6);
+ nsvg__xformInverse(shape->fill.gradient->xform, t);
+ }
+ if (shape->stroke.type == NSVG_PAINT_LINEAR_GRADIENT ||
+ shape->stroke.type == NSVG_PAINT_RADIAL_GRADIENT) {
+ nsvg__scaleGradient(shape->stroke.gradient, tx, ty, sx, sy);
+ memcpy(t, shape->stroke.gradient->xform, sizeof(float) * 6);
+ nsvg__xformInverse(shape->stroke.gradient->xform, t);
+ }
+
+ shape->strokeWidth *= avgs;
+ shape->strokeDashOffset *= avgs;
+ for (i = 0; i < shape->strokeDashCount; i++)
+ shape->strokeDashArray[i] *= avgs;
+ }
+}
+
+NSVGimage *nsvgParse(char *input, const char *units, float dpi)
+{
+ NSVGparser *p;
+ NSVGimage *ret = 0;
+
+ p = nsvg__createParser();
+ if (p == NULL) {
+ return NULL;
+ }
+ p->dpi = dpi;
+
+ nsvg__parseXML(input, nsvg__startElement, nsvg__endElement, nsvg__content, p);
+
+ // Scale to viewBox
+ nsvg__scaleToViewbox(p, units);
+
+ ret = p->image;
+ p->image = NULL;
+
+ nsvg__deleteParser(p);
+
+ return ret;
+}
+
+NSVGimage *nsvgParseFromFile(const char *filename, const char *units, float dpi)
+{
+ FILE *fp = NULL;
+ size_t size;
+ char *data = NULL;
+ NSVGimage *image = NULL;
+
+ fp = fopen(filename, "rb");
+ if (!fp)
+ goto error;
+ fseek(fp, 0, SEEK_END);
+ size = ftell(fp);
+ fseek(fp, 0, SEEK_SET);
+ data = (char *)malloc(size + 1);
+ if (data == NULL)
+ goto error;
+ if (fread(data, 1, size, fp) != size)
+ goto error;
+ data[size] = '\0'; // Must be null terminated.
+ fclose(fp);
+ image = nsvgParse(data, units, dpi);
+ free(data);
+
+ return image;
+
+error:
+ if (fp)
+ fclose(fp);
+ if (data)
+ free(data);
+ if (image)
+ nsvgDelete(image);
+ return NULL;
+}
+
+NSVGpath *nsvgDuplicatePath(NSVGpath *p)
+{
+ NSVGpath *res = NULL;
+
+ if (p == NULL)
+ return NULL;
+
+ res = (NSVGpath *)malloc(sizeof(NSVGpath));
+ if (res == NULL)
+ goto error;
+ memset(res, 0, sizeof(NSVGpath));
+
+ res->pts = (float *)malloc(p->npts * 2 * sizeof(float));
+ if (res->pts == NULL)
+ goto error;
+ memcpy(res->pts, p->pts, p->npts * sizeof(float) * 2);
+ res->npts = p->npts;
+
+ memcpy(res->bounds, p->bounds, sizeof(p->bounds));
+
+ res->closed = p->closed;
+
+ return res;
+
+error:
+ if (res != NULL) {
+ free(res->pts);
+ free(res);
+ }
+ return NULL;
+}
+
+void nsvgDelete(NSVGimage *image)
+{
+ NSVGshape *snext, *shape;
+ if (image == NULL)
+ return;
+ shape = image->shapes;
+ while (shape != NULL) {
+ snext = shape->next;
+ nsvg__deletePaths(shape->paths);
+ nsvg__deletePaint(&shape->fill);
+ nsvg__deletePaint(&shape->stroke);
+ free(shape);
+ shape = snext;
+ }
+ free(image);
+}
+
+#endif
diff --git a/source/blender/python/intern/CMakeLists.txt b/source/blender/python/intern/CMakeLists.txt
index c7816aed3c1..9ac8d4d9f47 100644
--- a/source/blender/python/intern/CMakeLists.txt
+++ b/source/blender/python/intern/CMakeLists.txt
@@ -363,4 +363,12 @@ if(WITH_POTRACE)
add_definitions(-DWITH_POTRACE)
endif()
+if(WITH_PUGIXML)
+ add_definitions(-DWITH_PUGIXML)
+endif()
+
+if(WITH_HARU)
+ add_definitions(-DWITH_HARU)
+endif()
+
blender_add_lib(bf_python "${SRC}" "${INC}" "${INC_SYS}" "${LIB}")
diff --git a/source/blender/python/intern/bpy_app_build_options.c b/source/blender/python/intern/bpy_app_build_options.c
index 9f12c9f80f1..676d1b8045f 100644
--- a/source/blender/python/intern/bpy_app_build_options.c
+++ b/source/blender/python/intern/bpy_app_build_options.c
@@ -65,6 +65,8 @@ static PyStructSequence_Field app_builtopts_info_fields[] = {
{"fluid", NULL},
{"xr_openxr", NULL},
{"potrace", NULL},
+ {"pugixml", NULL},
+ {"haru", NULL},
/* Sentinel (this line prevents `clang-format` wrapping into columns). */
{NULL},
};
@@ -311,6 +313,18 @@ static PyObject *make_builtopts_info(void)
SetObjIncref(Py_False);
#endif
+#ifdef WITH_PUGIXML
+ SetObjIncref(Py_True);
+#else
+ SetObjIncref(Py_False);
+#endif
+
+#ifdef WITH_HARU
+ SetObjIncref(Py_True);
+#else
+ SetObjIncref(Py_False);
+#endif
+
#undef SetObjIncref
return builtopts_info;