Files
MAX_CARLINK_A270S/MXC_A27-PCB4.5-270S/lib/awtk/awtk/src/svg/bsvg_draw.c
2025-01-21 16:49:37 +08:00

606 lines
17 KiB
C

/**
* File: bsvg_draw.c
* Author: AWTK Develop Team
* Brief: bsvg_draw
*
* Copyright (c) 2018 - 2021 Guangzhou ZHIYUAN Electronics Co.,Ltd.
*
* 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
* License file for more details.
*
*/
/**
* History:
* ================================================================
* 2018-11-20 Li XianJing <xianjimli@hotmail.com> created
*
*/
#include "svg/bsvg_draw.h"
/*https://www.w3.org/TR/SVG11/implnote.html#ArcImplementationNotes*/
typedef struct _draw_ctx_t {
float x;
float y;
bsvg_t* bsvg;
vgcanvas_t* canvas;
const svg_shape_t* shape;
/*for S/S_REL*/
float last_x2;
float last_y2;
uint8_t last_type;
/*for T/T_REL*/
float last_x1;
float last_y1;
} draw_ctx_t;
static ret_t bsvg_draw_arc_path(draw_ctx_t* ctx, const svg_path_arc_t* arc);
ret_t bsvg_draw_path(draw_ctx_t* ctx, const svg_path_t* path) {
vgcanvas_t* canvas = ctx->canvas;
switch (path->type) {
case SVG_PATH_M: {
const svg_path_move_t* p = (const svg_path_move_t*)path;
ctx->x = p->x;
ctx->y = p->y;
vgcanvas_move_to(canvas, ctx->x, ctx->y);
/* 屏蔽了 vg 的路径镂空函数,修复 gles 和 agge 的 svg 效果同步的问题,会导致 svg 没有了路径消除的逻辑 */
// vgcanvas_path_winding(canvas, 0);
break;
}
case SVG_PATH_M_REL: {
const svg_path_move_t* p = (const svg_path_move_t*)path;
ctx->x += p->x;
ctx->y += p->y;
vgcanvas_move_to(canvas, ctx->x, ctx->y);
/* 屏蔽了 vg 的路径镂空函数,修复 gles 和 agge 的 svg 效果同步的问题,会导致 svg 没有了路径消除的逻辑 */
// vgcanvas_path_winding(canvas, 0);
break;
}
case SVG_PATH_L: {
const svg_path_line_t* p = (const svg_path_line_t*)path;
ctx->x = p->x;
ctx->y = p->y;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_L_REL: {
const svg_path_line_t* p = (const svg_path_line_t*)path;
ctx->x += p->x;
ctx->y += p->y;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_H: {
const svg_path_hline_t* p = (const svg_path_hline_t*)path;
ctx->x = p->x;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_H_REL: {
const svg_path_hline_t* p = (const svg_path_hline_t*)path;
ctx->x += p->x;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_V: {
const svg_path_vline_t* p = (const svg_path_vline_t*)path;
ctx->y = p->y;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_V_REL: {
const svg_path_vline_t* p = (const svg_path_vline_t*)path;
ctx->y += p->y;
vgcanvas_line_to(canvas, ctx->x, ctx->y);
break;
}
case SVG_PATH_C: {
const svg_path_curve_to_t* p = (const svg_path_curve_to_t*)path;
vgcanvas_bezier_to(canvas, p->x1, p->y1, p->x2, p->y2, p->x, p->y);
ctx->x = p->x;
ctx->y = p->y;
ctx->last_x2 = p->x2;
ctx->last_y2 = p->y2;
break;
}
case SVG_PATH_C_REL: {
const svg_path_curve_to_t* p = (const svg_path_curve_to_t*)path;
float x1 = ctx->x + p->x1;
float y1 = ctx->y + p->y1;
float x2 = ctx->x + p->x2;
float y2 = ctx->y + p->y2;
float x = ctx->x + p->x;
float y = ctx->y + p->y;
vgcanvas_bezier_to(canvas, x1, y1, x2, y2, x, y);
ctx->x = x;
ctx->y = y;
ctx->last_x2 = x2;
ctx->last_y2 = y2;
break;
}
case SVG_PATH_S: {
const svg_path_scurve_to_t* p = (const svg_path_scurve_to_t*)path;
float x1 = ctx->x;
float y1 = ctx->y;
if (ctx->last_type == SVG_PATH_C_REL || ctx->last_type == SVG_PATH_C ||
ctx->last_type == SVG_PATH_S_REL || ctx->last_type == SVG_PATH_S) {
x1 = 2 * ctx->x - ctx->last_x2;
y1 = 2 * ctx->y - ctx->last_y2;
}
vgcanvas_bezier_to(canvas, x1, y1, p->x2, p->y2, p->x, p->y);
ctx->x = p->x;
ctx->y = p->y;
ctx->last_x2 = p->x2;
ctx->last_y2 = p->y2;
break;
}
case SVG_PATH_S_REL: {
const svg_path_scurve_to_t* p = (const svg_path_scurve_to_t*)path;
float x1 = ctx->x;
float y1 = ctx->y;
float x2 = ctx->x + p->x2;
float y2 = ctx->y + p->y2;
float x = ctx->x + p->x;
float y = ctx->y + p->y;
if (ctx->last_type == SVG_PATH_C_REL || ctx->last_type == SVG_PATH_C ||
ctx->last_type == SVG_PATH_S_REL || ctx->last_type == SVG_PATH_S) {
x1 = 2 * ctx->x - ctx->last_x2;
y1 = 2 * ctx->y - ctx->last_y2;
}
vgcanvas_bezier_to(canvas, x1, y1, x2, y2, x, y);
ctx->x = x;
ctx->y = y;
ctx->last_x2 = x2;
ctx->last_y2 = y2;
break;
}
case SVG_PATH_Q: {
const svg_path_qcurve_to_t* p = (const svg_path_qcurve_to_t*)path;
vgcanvas_quad_to(canvas, p->x1, p->y1, p->x, p->y);
ctx->x = p->x;
ctx->y = p->y;
ctx->last_x1 = p->x1;
ctx->last_y1 = p->y1;
break;
}
case SVG_PATH_Q_REL: {
const svg_path_qcurve_to_t* p = (const svg_path_qcurve_to_t*)path;
float x1 = ctx->x + p->x1;
float y1 = ctx->y + p->y1;
float x = ctx->x + p->x;
float y = ctx->y + p->y;
vgcanvas_quad_to(canvas, x1, y1, x, y);
ctx->x = x;
ctx->y = y;
ctx->last_x1 = x1;
ctx->last_y1 = y1;
break;
}
case SVG_PATH_T: {
const svg_path_tcurve_to_t* p = (const svg_path_tcurve_to_t*)path;
float x1 = 2 * ctx->x - ctx->last_x1;
float y1 = 2 * ctx->y - ctx->last_y1;
vgcanvas_quad_to(canvas, x1, y1, p->x, p->y);
ctx->x = p->x;
ctx->y = p->y;
ctx->last_x1 = x1;
ctx->last_y1 = y1;
break;
}
case SVG_PATH_T_REL: {
const svg_path_tcurve_to_t* p = (const svg_path_tcurve_to_t*)path;
float x1 = 2 * ctx->x - ctx->last_x1;
float y1 = 2 * ctx->y - ctx->last_y1;
float x = ctx->x + p->x;
float y = ctx->y + p->y;
vgcanvas_quad_to(canvas, x1, y1, x, y);
ctx->x = x;
ctx->y = y;
ctx->last_x1 = x1;
ctx->last_y1 = y1;
break;
}
case SVG_PATH_A: {
const svg_path_arc_t* p = (const svg_path_arc_t*)path;
bsvg_draw_arc_path(ctx, p);
break;
}
case SVG_PATH_A_REL: {
const svg_path_arc_t* p = (const svg_path_arc_t*)path;
bsvg_draw_arc_path(ctx, p);
break;
}
case SVG_PATH_Z: {
vgcanvas_close_path(canvas);
break;
}
case SVG_PATH_NULL:
default: {
break;
}
}
ctx->last_type = path->type;
return RET_OK;
}
static ret_t bsvg_draw_set_line_cap(vgcanvas_t* canvas, char line_cap) {
if (line_cap == 'r') {
vgcanvas_set_line_cap(canvas, "round");
} else if (line_cap == 's') {
vgcanvas_set_line_cap(canvas, "square");
} else {
vgcanvas_set_line_cap(canvas, "butt");
}
return RET_OK;
}
static ret_t bsvg_draw_set_line_join(vgcanvas_t* canvas, char line_join) {
if (line_join == 'r') {
vgcanvas_set_line_join(canvas, "round");
} else if (line_join == 'b') {
vgcanvas_set_line_join(canvas, "bevel");
} else {
vgcanvas_set_line_join(canvas, "miter");
}
return RET_OK;
}
ret_t bsvg_draw_shape_end(draw_ctx_t* ctx, const svg_shape_t* shape) {
vgcanvas_t* canvas = ctx->canvas;
bsvg_header_t* header = ctx->bsvg->header;
if (shape->line_cap) {
bsvg_draw_set_line_cap(canvas, shape->line_cap);
} else if (header->line_cap) {
bsvg_draw_set_line_cap(canvas, header->line_cap);
}
if (shape->line_join) {
bsvg_draw_set_line_join(canvas, shape->line_join);
} else if (header->line_join) {
bsvg_draw_set_line_join(canvas, header->line_join);
}
if (shape->stroke_width) {
vgcanvas_set_line_width(canvas, shape->stroke_width);
} else if (header->stroke_width) {
vgcanvas_set_line_width(canvas, header->stroke_width);
} else {
vgcanvas_set_line_width(canvas, 1);
}
if (shape->fill.rgba.a) {
vgcanvas_set_fill_color(canvas, shape->fill);
} else if (header->fill.rgba.a) {
vgcanvas_set_fill_color(canvas, header->fill);
}
if (!shape->no_fill) {
vgcanvas_fill(canvas);
}
if (shape->stroke.rgba.a) {
vgcanvas_set_stroke_color(canvas, shape->stroke);
} else if (header->stroke.rgba.a) {
vgcanvas_set_stroke_color(canvas, header->stroke);
}
if (!shape->no_stroke) {
vgcanvas_stroke(canvas);
}
return RET_OK;
}
ret_t bsvg_draw_shape(draw_ctx_t* ctx, const svg_shape_t* shape) {
vgcanvas_t* canvas = ctx->canvas;
vgcanvas_begin_path(canvas);
switch (shape->type) {
case SVG_SHAPE_RECT: {
svg_shape_rect_t* s = (svg_shape_rect_t*)shape;
vgcanvas_rounded_rect(canvas, s->x, s->y, s->w, s->h, s->r);
break;
}
case SVG_SHAPE_CIRCLE: {
svg_shape_circle_t* s = (svg_shape_circle_t*)shape;
vgcanvas_arc(canvas, s->cx, s->cy, s->r, 0, M_PI * 2, FALSE);
break;
}
case SVG_SHAPE_ELLIPSE: {
svg_shape_ellipse_t* s = (svg_shape_ellipse_t*)shape;
vgcanvas_ellipse(canvas, s->cx, s->cy, s->rx, s->ry);
break;
}
case SVG_SHAPE_LINE: {
svg_shape_line_t* s = (svg_shape_line_t*)shape;
vgcanvas_move_to(canvas, s->x1, s->y1);
vgcanvas_line_to(canvas, s->x2, s->y2);
break;
}
case SVG_SHAPE_POLYGON: {
uint32_t i = 0;
svg_shape_polygon_t* s = (svg_shape_polygon_t*)shape;
for (i = 0; i < s->nr; i += 2) {
float x = s->data[i];
float y = s->data[i + 1];
if (i == 0) {
vgcanvas_move_to(canvas, x, y);
} else {
vgcanvas_line_to(canvas, x, y);
}
}
break;
}
case SVG_SHAPE_POLYLINE: {
uint32_t i = 0;
svg_shape_polygon_t* s = (svg_shape_polygon_t*)shape;
for (i = 0; i < s->nr; i += 2) {
float x = s->data[i];
float y = s->data[i + 1];
if (i == 0) {
vgcanvas_move_to(canvas, x, y);
} else {
vgcanvas_line_to(canvas, x, y);
}
}
vgcanvas_close_path(canvas);
break;
}
case SVG_SHAPE_PATH: {
return RET_OK;
}
case SVG_SHAPE_NULL: {
return RET_OK;
}
default: {
assert(!"not supported type");
return RET_OK;
}
}
return bsvg_draw_shape_end(ctx, shape);
}
static ret_t bsvg_draw_on_path(void* ctx, const void* data) {
draw_ctx_t* info = (draw_ctx_t*)ctx;
const svg_path_t* path = (const svg_path_t*)data;
if (path->type == SVG_PATH_NULL) {
return bsvg_draw_shape_end(info, info->shape);
} else {
bsvg_draw_path(info, path);
}
return RET_OK;
}
static ret_t bsvg_draw_on_shape(void* ctx, const void* data) {
draw_ctx_t* info = (draw_ctx_t*)ctx;
const svg_shape_t* shape = (const svg_shape_t*)data;
info->shape = shape;
bsvg_draw_shape(info, shape);
return RET_OK;
}
ret_t bsvg_draw(bsvg_t* svg, vgcanvas_t* canvas) {
float sx = 1;
float sy = 1;
draw_ctx_t ctx;
bsvg_header_t* header = svg->header;
return_value_if_fail(header != NULL && svg != NULL && canvas != NULL, RET_BAD_PARAMS);
ctx.x = 0;
ctx.y = 0;
ctx.bsvg = svg;
ctx.shape = NULL;
ctx.canvas = canvas;
vgcanvas_save(canvas);
if (header->w && header->h && header->viewport.w && header->viewport.h) {
sx = (float)(header->w) / (float)(header->viewport.w);
sy = (float)(header->h) / (float)(header->viewport.h);
}
vgcanvas_scale(canvas, sx, sy);
bsvg_visit(svg, &ctx, bsvg_draw_on_shape, bsvg_draw_on_path);
vgcanvas_restore(canvas);
return RET_OK;
}
/*Adapt from https://github.com/mozilla/newtab-dev/blob/master/dom/svg/nsSVGPathDataParser.cpp
* begin:*/
typedef struct _arc_info_t {
uint32_t seg_index;
uint32_t num_segs;
float rx;
float ry;
float sin_phi;
float cos_phi;
pointf_t c;
float delta;
pointf_t from;
float t;
float theta;
} arc_info_t;
static float calc_vector_angle(float ux, float uy, float vx, float vy) {
float ta = atan2(uy, ux);
float tb = atan2(vy, vx);
if (tb >= ta) return tb - ta;
return 2 * M_PI - (ta - tb);
}
static ret_t arc_info_init(arc_info_t* info, pointf_t from, pointf_t to, pointf_t radii,
float angle, bool_t largeArcFlag, bool_t sweepFlag) {
const float radPerDeg = M_PI / 180.0f;
info->seg_index = 0;
if (from.x == to.x && from.y == to.y) {
info->num_segs = 0;
return RET_OK;
}
/*
// Convert to center parameterization as shown in
// http://www.w3.org/TR/SVG/implnote.html
*/
info->rx = fabs(radii.x);
info->ry = fabs(radii.y);
info->sin_phi = sin(angle * radPerDeg);
info->cos_phi = cos(angle * radPerDeg);
float x1dash = info->cos_phi * (from.x - to.x) / 2.0f + info->sin_phi * (from.y - to.y) / 2.0f;
float y1dash = -info->sin_phi * (from.x - to.x) / 2.0f + info->cos_phi * (from.y - to.y) / 2.0f;
float root;
float numerator = info->rx * info->rx * info->ry * info->ry -
info->rx * info->rx * y1dash * y1dash - info->ry * info->ry * x1dash * x1dash;
if (numerator < 0.0f) {
/*
// If info->rx , info->ry and are such that there is no solution (basically,
// the ellipse is not big enough to reach from 'from' to 'to'
// then the ellipse is scaled up uniformly until there is
// exactly one solution (until the ellipse is just big enough).
// -> find factor s, such that numerator' with info->rx'=s*info->rx and
// info->ry'=s*info->ry becomes 0 :
*/
float s = sqrt(1.0f - numerator / (info->rx * info->rx * info->ry * info->ry));
info->rx *= s;
info->ry *= s;
root = 0.0f;
} else {
root = (largeArcFlag == sweepFlag ? -1.0f : 1.0f) *
sqrt(numerator /
(info->rx * info->rx * y1dash * y1dash + info->ry * info->ry * x1dash * x1dash));
}
float cxdash = root * info->rx * y1dash / info->ry;
float cydash = -root * info->ry * x1dash / info->rx;
info->c.x = info->cos_phi * cxdash - info->sin_phi * cydash + (from.x + to.x) / 2.0f;
info->c.y = info->sin_phi * cxdash + info->cos_phi * cydash + (from.y + to.y) / 2.0f;
info->theta =
calc_vector_angle(1.0f, 0.0f, (x1dash - cxdash) / info->rx, (y1dash - cydash) / info->ry);
float dtheta = calc_vector_angle((x1dash - cxdash) / info->rx, (y1dash - cydash) / info->ry,
(-x1dash - cxdash) / info->rx, (-y1dash - cydash) / info->ry);
if (!sweepFlag && dtheta > 0)
dtheta -= 2.0f * M_PI;
else if (sweepFlag && dtheta < 0)
dtheta += 2.0f * M_PI;
/* Convert into cubic bezier segments <= 90deg */
info->num_segs = (int32_t)(ceil(fabs(dtheta / (M_PI / 2.0f))));
info->delta = dtheta / info->num_segs;
info->t =
8.0f / 3.0f * sin(info->delta / 4.0f) * sin(info->delta / 4.0f) / sin(info->delta / 2.0f);
info->from = from;
return RET_OK;
}
bool_t arc_info_next(arc_info_t* info, pointf_t* cp1, pointf_t* cp2, pointf_t* to) {
if (info->seg_index == info->num_segs) {
return FALSE;
}
float cosTheta1 = cos(info->theta);
float sinTheta1 = sin(info->theta);
float theta2 = info->theta + info->delta;
float cosTheta2 = cos(theta2);
float sinTheta2 = sin(theta2);
/* a) calculate endpoint of the segment:*/
to->x = info->cos_phi * info->rx * cosTheta2 - info->sin_phi * info->ry * sinTheta2 + info->c.x;
to->y = info->sin_phi * info->rx * cosTheta2 + info->cos_phi * info->ry * sinTheta2 + info->c.y;
/* b) calculate gradients at start/end points of segment:*/
cp1->x = info->from.x +
info->t * (-info->cos_phi * info->rx * sinTheta1 - info->sin_phi * info->ry * cosTheta1);
cp1->y = info->from.y +
info->t * (-info->sin_phi * info->rx * sinTheta1 + info->cos_phi * info->ry * cosTheta1);
cp2->x = to->x +
info->t * (info->cos_phi * info->rx * sinTheta2 + info->sin_phi * info->ry * cosTheta2);
cp2->y = to->y +
info->t * (info->sin_phi * info->rx * sinTheta2 - info->cos_phi * info->ry * cosTheta2);
/* do next segment*/
info->theta = theta2;
info->from = *to;
++info->seg_index;
return TRUE;
}
/*Adapt from https://github.com/mozilla/newtab-dev/blob/master/dom/svg/nsSVGPathDataParser.cpp end*/
static ret_t bsvg_draw_arc_path(draw_ctx_t* ctx, const svg_path_arc_t* arc) {
arc_info_t info;
pointf_t cp1 = {0, 0};
pointf_t cp2 = {0, 0};
pointf_t end = {0, 0};
pointf_t r = {arc->rx, arc->ry};
pointf_t from = {ctx->x, ctx->y};
pointf_t to = {arc->x, arc->y};
vgcanvas_t* canvas = ctx->canvas;
if (arc->path.type == SVG_PATH_A_REL) {
to.x += ctx->x;
to.y += ctx->y;
}
arc_info_init(&info, from, to, r, arc->rotation, arc->large_arc, arc->sweep);
while (arc_info_next(&info, &cp1, &cp2, &end)) {
vgcanvas_bezier_to(canvas, cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
}
ctx->x = to.x;
ctx->y = to.y;
return RET_OK;
}