blob: 950624350ea841c0638e74ebf401ea460923ed3a [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "tests/common/svg/svg_utils.h"
#include <math.h>
#include "tests/common/svg/svg_path_sink.h"
bool
svg_decode_path(const struct svg * svg,
uint32_t path_id,
const affine_transform_t * transform,
PathSink * target)
{
bool result = true;
SvgPathSink svg_sink(target, transform);
struct svg_path_iterator * iterator = svg_path_iterator_create(svg, path_id);
union svg_path_cmd const * cmd;
while (svg_path_iterator_next(iterator, &cmd))
{
switch (cmd->type)
{
case SVG_PATH_CMD_BEGIN:
break;
case SVG_PATH_CMD_END:
break;
case SVG_PATH_CMD_CIRCLE:
svg_sink.circle(cmd->circle.cx, cmd->circle.cy, cmd->circle.r);
break;
case SVG_PATH_CMD_ELLIPSE:
svg_sink.ellipse(cmd->ellipse.cx, cmd->ellipse.cy, cmd->ellipse.rx, cmd->ellipse.ry);
break;
case SVG_PATH_CMD_LINE:
svg_sink.line(cmd->line.x1, cmd->line.y1, cmd->line.x2, cmd->line.y2);
break;
case SVG_PATH_CMD_POLYGON:
svg_sink.polyStart(true);
// This is followed by one or more SVG_PATH_CMD_POLY_POINT elements.
break;
case SVG_PATH_CMD_POLYLINE:
svg_sink.polyStart(false);
// This is followed by one or more SVG_PATH_CMD_POLY_POINT elements.
break;
case SVG_PATH_CMD_POLY_POINT:
svg_sink.polyPoint(cmd->poly_point.x, cmd->poly_point.y);
break;
case SVG_PATH_CMD_POLY_END:
svg_sink.polyEnd();
break;
case SVG_PATH_CMD_RECT:
svg_sink.rect(cmd->rect.x, cmd->rect.y, cmd->rect.width, cmd->rect.height);
break;
case SVG_PATH_CMD_PATH_BEGIN:
svg_sink.pathBegin();
break;
case SVG_PATH_CMD_PATH_END:
if (!svg_sink.pathEnd())
{
result = false;
goto Exit;
}
break;
case SVG_PATH_CMD_MOVE_TO:
svg_sink.moveTo(cmd->move_to.x, cmd->move_to.y);
break;
case SVG_PATH_CMD_MOVE_TO_REL:
svg_sink.moveTo(cmd->move_to.x, cmd->move_to.y, true);
break;
case SVG_PATH_CMD_CLOSE_UPPER:
case SVG_PATH_CMD_CLOSE:
svg_sink.pathClose();
break;
case SVG_PATH_CMD_LINE_TO:
svg_sink.lineTo(cmd->line_to.x, cmd->line_to.y);
break;
case SVG_PATH_CMD_LINE_TO_REL:
svg_sink.lineTo(cmd->line_to.x, cmd->line_to.y, true);
break;
case SVG_PATH_CMD_HLINE_TO:
svg_sink.hlineTo(cmd->hline_to.c);
break;
case SVG_PATH_CMD_HLINE_TO_REL:
svg_sink.hlineTo(cmd->hline_to.c, true);
break;
case SVG_PATH_CMD_VLINE_TO:
svg_sink.vlineTo(cmd->vline_to.c);
break;
case SVG_PATH_CMD_VLINE_TO_REL:
svg_sink.vlineTo(cmd->vline_to.c, true);
break;
case SVG_PATH_CMD_CUBIC_TO:
case SVG_PATH_CMD_CUBIC_TO_REL:
svg_sink.cubicTo(cmd->cubic_to.x1,
cmd->cubic_to.y1,
cmd->cubic_to.x2,
cmd->cubic_to.y2,
cmd->cubic_to.x,
cmd->cubic_to.y,
cmd->type == SVG_PATH_CMD_CUBIC_TO_REL);
break;
case SVG_PATH_CMD_CUBIC_SMOOTH_TO:
case SVG_PATH_CMD_CUBIC_SMOOTH_TO_REL:
svg_sink.smoothCubicTo(cmd->cubic_smooth_to.x2,
cmd->cubic_smooth_to.y2,
cmd->cubic_smooth_to.x,
cmd->cubic_smooth_to.y,
cmd->type == SVG_PATH_CMD_CUBIC_SMOOTH_TO_REL);
break;
case SVG_PATH_CMD_QUAD_TO:
case SVG_PATH_CMD_QUAD_TO_REL:
svg_sink.quadTo(cmd->quad_to.x1,
cmd->quad_to.y1,
cmd->quad_to.x,
cmd->quad_to.y,
cmd->type == SVG_PATH_CMD_QUAD_TO_REL);
break;
case SVG_PATH_CMD_QUAD_SMOOTH_TO:
case SVG_PATH_CMD_QUAD_SMOOTH_TO_REL:
svg_sink.smoothQuadTo(cmd->quad_smooth_to.x,
cmd->quad_smooth_to.y,
cmd->type == SVG_PATH_CMD_QUAD_SMOOTH_TO_REL);
break;
case SVG_PATH_CMD_RAT_QUAD_TO:
case SVG_PATH_CMD_RAT_QUAD_TO_REL:
svg_sink.ratQuadTo(cmd->rat_quad_to.x1,
cmd->rat_quad_to.y1,
cmd->rat_quad_to.x,
cmd->rat_quad_to.y,
cmd->rat_quad_to.w1,
cmd->type == SVG_PATH_CMD_RAT_QUAD_TO_REL);
break;
case SVG_PATH_CMD_RAT_CUBIC_TO:
case SVG_PATH_CMD_RAT_CUBIC_TO_REL:
svg_sink.ratCubicTo(cmd->rat_cubic_to.x1,
cmd->rat_cubic_to.y1,
cmd->rat_cubic_to.x2,
cmd->rat_cubic_to.y2,
cmd->rat_cubic_to.x,
cmd->rat_cubic_to.y,
cmd->rat_cubic_to.w1,
cmd->rat_cubic_to.w2,
cmd->type == SVG_PATH_CMD_RAT_CUBIC_TO_REL);
break;
case SVG_PATH_CMD_ARC_TO:
case SVG_PATH_CMD_ARC_TO_REL:
svg_sink.arcTo(cmd->arc_to.x,
cmd->arc_to.y,
cmd->arc_to.rx,
cmd->arc_to.ry,
cmd->arc_to.x_axis_rotation * (M_PI / 180.),
cmd->arc_to.large_arc_flag != 0,
cmd->arc_to.sweep_flag != 0,
cmd->type == SVG_PATH_CMD_ARC_TO_REL);
break;
}
}
Exit:
svg_path_iterator_dispose(iterator);
return result;
}
// Simple affine transform stack for SVG raster decoding.
class TransformStack {
public:
TransformStack(const affine_transform_t * transform)
{
stack_.push_back(transform ? *transform : affine_transform_identity);
depth_ = 1;
}
const affine_transform_t *
current() const
{
return &stack_.back();
}
void
push(affine_transform_t transform)
{
// IMPORTANT: Svg transforms must be applied in reversed push order
// which requires: T + [A B C ...] => [(A * T) A B C ...]
stack_.push_back(affine_transform_multiply(current(), &transform));
}
void
pop()
{
if (stack_.size() > depth_)
stack_.pop_back();
}
private:
std::vector<affine_transform_t> stack_;
uint32_t depth_;
};
bool
svg_decode_rasters(const struct svg * svg,
const affine_transform_t * transform,
SvgDecodedRaster::Callback callback)
{
bool result = true;
TransformStack transforms(transform);
struct svg_raster_iterator * iterator = svg_raster_iterator_create(svg, UINT32_MAX);
union svg_raster_cmd const * cmd;
uint32_t raster_id = 0;
while (svg_raster_iterator_next(iterator, &cmd))
{
switch (cmd->type)
{
case SVG_RASTER_CMD_BEGIN:
// NOTE: Starting a new raster does *not* reset the transform stack.
// instead, stack changes are carried from one raster to the next one
// in the command list (making it impossible to decode an individual
// raster properly without decoding all previous ones previously).
break;
case SVG_RASTER_CMD_END:
raster_id++;
break;
case SVG_RASTER_CMD_FILL:
result = callback((SvgDecodedRaster){
.svg = svg,
.raster_id = raster_id,
.path_id = cmd->fill.path_index,
.transform = *transforms.current(),
});
if (!result)
goto Exit;
break;
case SVG_RASTER_CMD_STROKE: // TODO(digit):
case SVG_RASTER_CMD_MARKER: // TODO(digit):
case SVG_RASTER_CMD_STROKE_WIDTH: // TODO(digit):
break;
case SVG_RASTER_CMD_TRANSFORM_PROJECT:
case SVG_RASTER_CMD_TRANSFORM_MATRIX:
transforms.push((const affine_transform_t){
.sx = cmd->matrix.sx,
.shx = cmd->matrix.shx,
.shy = cmd->matrix.shy,
.sy = cmd->matrix.sy,
.tx = cmd->matrix.tx,
.ty = cmd->matrix.ty,
});
break;
case SVG_RASTER_CMD_TRANSFORM_TRANSLATE:
transforms.push((const affine_transform_t){
.sx = 1.,
.sy = 1.,
.tx = cmd->translate.tx,
.ty = cmd->translate.ty,
});
break;
case SVG_RASTER_CMD_TRANSFORM_SCALE:
transforms.push((const affine_transform_t){
.sx = cmd->scale.sx,
.sy = cmd->scale.sy,
});
break;
case SVG_RASTER_CMD_TRANSFORM_ROTATE: {
transforms.push(affine_transform_make_rotation_xy(cmd->rotate.d * M_PI / 180.0,
cmd->rotate.cx,
cmd->rotate.cy));
break;
}
case SVG_RASTER_CMD_TRANSFORM_SKEW_X: {
transforms.push(affine_transform_make_skew_x(cmd->skew_x.d));
break;
}
case SVG_RASTER_CMD_TRANSFORM_SKEW_Y: {
transforms.push(affine_transform_make_skew_y(cmd->skew_y.d));
break;
}
case SVG_RASTER_CMD_TRANSFORM_DROP:
transforms.pop();
break;
}
}
Exit:
svg_raster_iterator_dispose(iterator);
return result;
}
bool
svg_decode_layers(const svg * svg, SvgDecodedLayer::Callback callback)
{
bool result = true;
svg_layer_iterator * iter = svg_layer_iterator_create(svg, UINT32_MAX);
const union svg_layer_cmd * cmd;
SvgDecodedLayer layer = {
.svg = svg,
.layer_id = 0,
.fill_color = 0,
.fill_opacity = 1.,
.opacity = 1.,
.fill_even_odd = false,
.prints = {},
};
while (svg_layer_iterator_next(iter, &cmd))
{
switch (cmd->type)
{
case SVG_LAYER_CMD_BEGIN:
break;
case SVG_LAYER_CMD_END:
result = callback(layer);
if (!result)
goto Exit;
layer.layer_id++;
layer.prints.clear();
break;
case SVG_LAYER_CMD_PLACE: {
layer.prints.push_back(SvgDecodedLayer::Print{
.raster_id = cmd->place.raster_index,
.tx = cmd->place.tx,
.ty = cmd->place.ty,
});
break;
}
case SVG_LAYER_CMD_OPACITY:
layer.opacity = cmd->opacity.opacity;
break;
case SVG_LAYER_CMD_FILL_RULE:
layer.fill_even_odd = (cmd->fill_rule.fill_rule == SVG_FILL_RULE_OP_EVENODD);
break;
case SVG_LAYER_CMD_FILL_COLOR:
layer.fill_color = cmd->fill_color.fill_color;
break;
case SVG_LAYER_CMD_FILL_OPACITY:
layer.fill_opacity = cmd->fill_opacity.fill_opacity;
break;
case SVG_LAYER_CMD_STROKE_COLOR:
case SVG_LAYER_CMD_STROKE_OPACITY:
// IGNORED FOR NOW.
break;
}
}
Exit:
svg_layer_iterator_dispose(iter);
return result;
}