* Copyright 2006, Haiku. All rights reserved.
* Distributed under the terms of the MIT License.
*
* Authors:
* Stephan Aßmus <superstippi@gmx.de>
*/
#define NANOSVG_IMPLEMENTATION
#include "DocumentBuilder.h"
#include <new>
#include <stdio.h>
#include <Bitmap.h>
#include "AutoDeleter.h"
#include "GradientTransformable.h"
#include "Icon.h"
#include "PathSourceShape.h"
#include "StrokeTransformer.h"
#include "Style.h"
#include "SVGImporter.h"
#include "VectorPath.h"
#include <agg_math_stroke.h>
#include <agg_trans_affine.h>
using std::nothrow;
DocumentBuilder::DocumentBuilder(NSVGimage* source)
: fWidth(0),
fHeight(0),
fViewBox(0.0, 0.0, -1.0, -1.0),
fTitle(""),
fSource(source)
{
}
void
DocumentBuilder::SetTitle(const char* title)
{
fTitle = title;
}
void
DocumentBuilder::SetDimensions(uint32 width, uint32 height, BRect viewBox)
{
fWidth = width;
fHeight = height;
fViewBox = viewBox;
}
status_t
DocumentBuilder::GetIcon(Icon* icon, const char* fallbackName)
{
double xMin = 0;
double yMin = 0;
double xMax = ceil(fSource->width);
double yMax = ceil(fSource->height);
BRect bounds;
if (fViewBox.IsValid()) {
bounds = fViewBox;
printf("view box: ");
bounds.PrintToStream();
} else {
bounds.Set(0.0, 0.0, (int32)fWidth - 1, (int32)fHeight - 1);
printf("width/height: ");
bounds.PrintToStream();
}
BRect boundingBox(xMin, yMin, xMax, yMax);
if (!bounds.IsValid() || !boundingBox.Intersects(bounds)) {
bounds = boundingBox;
printf("using bounding box: ");
bounds.PrintToStream();
}
float size = max_c(bounds.Width(), bounds.Height());
double scale = 64.0 / size;
printf("scale: %f\n", scale);
Transformable transform;
transform.TranslateBy(BPoint(-bounds.left, -bounds.top));
transform.ScaleBy(B_ORIGIN, scale, scale);
for (NSVGshape* shape = fSource->shapes; shape != NULL; shape = shape->next) {
if (shape->fill.type != NSVG_PAINT_NONE)
_AddShape(shape, false, transform, icon);
if (shape->stroke.type != NSVG_PAINT_NONE)
_AddShape(shape, true, transform, icon);
}
int32 count = icon->Shapes()->CountItems();
for (int32 i = 1; i < count; i++) {
PathSourceShape* shape = dynamic_cast<PathSourceShape*>(icon->Shapes()->ItemAtFast(i));
if (shape == NULL)
continue;
Style* style = shape->Style();
if (style == NULL)
continue;
int32 styleIndex = icon->Styles()->IndexOf(style);
for (int32 j = 0; j < styleIndex; j++) {
Style* earlierStyle = icon->Styles()->ItemAtFast(j);
if (*style == *earlierStyle) {
shape->SetStyle(earlierStyle);
icon->Styles()->RemoveItem(style);
style->ReleaseReference();
break;
}
}
}
return B_OK;
}
status_t
AddPathsFromVertexSource(Icon* icon, PathSourceShape* shape, NSVGshape* svgShape)
{
for (NSVGpath* svgPath = svgShape->paths; svgPath != NULL;
svgPath = svgPath->next) {
VectorPath* path = new (nothrow) VectorPath();
if (!path || !icon->Paths()->AddItem(path)) {
delete path;
return B_NO_MEMORY;
}
if (!shape->Paths()->AddItem(path))
return B_NO_MEMORY;
path->SetClosed(svgPath->closed);
int pointCount = svgPath->npts;
float* points = svgPath->pts;
if (!path->AddPoint(BPoint(points[0], points[1])))
return B_NO_MEMORY;
path->SetInOutConnected(path->CountPoints() - 1, false);
pointCount--;
points += 2;
while (pointCount > 0) {
BPoint vector1(points[0], points[1]);
BPoint vector2(points[2], points[3]);
BPoint endPoint(points[4], points[5]);
if (!path->AddPoint(endPoint))
return B_NO_MEMORY;
int32 start = path->CountPoints() - 2;
int32 end = path->CountPoints() - 1;
path->SetInOutConnected(end, false);
path->SetPointOut(start, vector1);
path->SetPointIn(end, vector2);
pointCount -= 3;
points += 6;
}
}
return B_OK;
}
status_t
DocumentBuilder::_AddShape(NSVGshape* svgShape, bool outline,
const Transformable& transform, Icon* icon)
{
PathSourceShape* shape = new (nothrow) PathSourceShape(NULL);
if (!shape || !icon->Shapes()->AddItem(shape)) {
delete shape;
return B_NO_MEMORY;
}
if (AddPathsFromVertexSource(icon, shape, svgShape) < B_OK)
printf("failed to convert from vertex source\n");
shape->SetName(svgShape->id);
shape->Multiply(transform);
StrokeTransformer* stroke = NULL;
NSVGpaint* paint = NULL;
if (outline) {
stroke = new (nothrow) StrokeTransformer(shape->VertexSource());
paint = &svgShape->stroke;
if (stroke) {
stroke->width(svgShape->strokeWidth);
switch(svgShape->strokeLineCap) {
case NSVG_CAP_BUTT:
stroke->line_cap(agg::butt_cap);
break;
case NSVG_CAP_ROUND:
stroke->line_cap(agg::round_cap);
break;
case NSVG_CAP_SQUARE:
stroke->line_cap(agg::square_cap);
break;
}
switch(svgShape->strokeLineJoin) {
case NSVG_JOIN_MITER:
stroke->line_join(agg::miter_join);
break;
case NSVG_JOIN_ROUND:
stroke->line_join(agg::round_join);
break;
case NSVG_JOIN_BEVEL:
stroke->line_join(agg::bevel_join);
break;
}
}
if (!shape->Transformers()->AddItem(stroke)) {
delete stroke;
stroke = NULL;
}
} else {
paint = &svgShape->fill;
#if 0
if (svgShape->fillRule == NSVG_FILLRULE_EVENODD)
shape->SetFillingRule(FILL_MODE_EVEN_ODD);
else
shape->SetFillingRule(FILL_MODE_NON_ZERO);
#endif
}
Gradient gradient(true);
rgb_color color;
switch(paint->type) {
case NSVG_PAINT_COLOR:
color.red = paint->color & 0xFF;
color.green = (paint->color >> 8) & 0xFF;
color.blue = (paint->color >> 16) & 0xFF;
color.alpha = (paint->color >> 24) & 0xFF;
break;
case NSVG_PAINT_LINEAR_GRADIENT:
{
gradient.SetType(GRADIENT_LINEAR);
agg::trans_affine baseTransform(0, 1.0/128.0, -1.0/128.0, 0,
-0.5, 0.5);
gradient.multiply(baseTransform);
break;
}
case NSVG_PAINT_RADIAL_GRADIENT:
{
gradient.SetType(GRADIENT_CIRCULAR);
agg::trans_affine baseTransform(0, 1.0/64.0, -1.0/64.0, 0,
0, 0);
gradient.multiply(baseTransform);
break;
}
}
if (paint->type != NSVG_PAINT_COLOR) {
gradient.SetInterpolation(INTERPOLATION_LINEAR);
agg::trans_affine gradientTransform(
paint->gradient->xform[0], paint->gradient->xform[1],
paint->gradient->xform[2], paint->gradient->xform[3],
paint->gradient->xform[4], paint->gradient->xform[5]
);
gradient.multiply_inv(gradientTransform);
gradient.Multiply(*shape);
for (int i = 0; i < paint->gradient->nstops; i++) {
rgb_color stopColor;
stopColor.red = paint->gradient->stops[i].color & 0xFF;
stopColor.green = (paint->gradient->stops[i].color >> 8) & 0xFF;
stopColor.blue = (paint->gradient->stops[i].color >> 16) & 0xFF;
stopColor.alpha = (paint->gradient->stops[i].color >> 24) & 0xFF;
gradient.AddColor(stopColor, paint->gradient->stops[i].offset);
}
BGradient::ColorStop* step = gradient.ColorAt(0);
if (step) {
color.red = step->color.red;
color.green = step->color.green;
color.blue = step->color.blue;
color.alpha = step->color.alpha;
}
}
color.alpha = (uint8)(color.alpha * svgShape->opacity);
Style* style = new (nothrow) Style(color);
if (!style || !icon->Styles()->AddItem(style)) {
delete style;
return B_NO_MEMORY;
}
int32 pathCount = shape->Paths()->CountItems();
for (int32 i = 0; i < pathCount; i++) {
VectorPath* path = shape->Paths()->ItemAtFast(i);
path->ApplyTransform(*shape);
}
if (stroke)
stroke->width(stroke->width() * shape->scale());
if (paint->type != NSVG_PAINT_COLOR)
style->SetGradient(&gradient);
shape->Reset();
shape->SetStyle(style);
return B_OK;
}