1/*
2 * Copyright 2006, Haiku. All rights reserved.
3 * Distributed under the terms of the MIT License.
4 *
5 * Authors:
6 *		Stephan A��mus <superstippi@gmx.de>
7 */
8
9
10#define NANOSVG_IMPLEMENTATION
11#include "DocumentBuilder.h"
12
13#include <new>
14#include <stdio.h>
15
16#include <Bitmap.h>
17
18#include "AutoDeleter.h"
19#include "GradientTransformable.h"
20#include "Icon.h"
21#include "PathSourceShape.h"
22#include "StrokeTransformer.h"
23#include "Style.h"
24#include "SVGImporter.h"
25#include "VectorPath.h"
26
27#include <agg_math_stroke.h>
28#include <agg_trans_affine.h>
29
30using std::nothrow;
31
32// constructor
33DocumentBuilder::DocumentBuilder(NSVGimage* source)
34	: fWidth(0),
35	  fHeight(0),
36	  fViewBox(0.0, 0.0, -1.0, -1.0),
37	  fTitle(""),
38	  fSource(source)
39{
40}
41
42
43// SetTitle
44void
45DocumentBuilder::SetTitle(const char* title)
46{
47	fTitle = title;
48}
49
50// SetDimensions
51void
52DocumentBuilder::SetDimensions(uint32 width, uint32 height, BRect viewBox)
53{
54	fWidth = width;
55	fHeight = height;
56	fViewBox = viewBox;
57}
58
59// #pragma mark -
60
61// GetIcon
62status_t
63DocumentBuilder::GetIcon(Icon* icon, const char* fallbackName)
64{
65	double xMin = 0;
66	double yMin = 0;
67	double xMax = ceil(fSource->width);
68	double yMax = ceil(fSource->height);
69
70	BRect bounds;
71	if (fViewBox.IsValid()) {
72		bounds = fViewBox;
73printf("view box: ");
74bounds.PrintToStream();
75	} else {
76		bounds.Set(0.0, 0.0, (int32)fWidth - 1, (int32)fHeight - 1);
77printf("width/height: ");
78bounds.PrintToStream();
79	}
80
81	BRect boundingBox(xMin, yMin, xMax, yMax);
82
83	if (!bounds.IsValid() || !boundingBox.Intersects(bounds)) {
84		bounds = boundingBox;
85printf("using bounding box: ");
86bounds.PrintToStream();
87	}
88
89	float size = max_c(bounds.Width(), bounds.Height());
90	double scale = 64.0 / size;
91printf("scale: %f\n", scale);
92
93	Transformable transform;
94	transform.TranslateBy(BPoint(-bounds.left, -bounds.top));
95	transform.ScaleBy(B_ORIGIN, scale, scale);
96
97//	if (fTitle.CountChars() > 0)
98//		icon->SetName(fTitle.String());
99//	else
100//		icon->SetName(fallbackName);
101
102	for (NSVGshape* shape = fSource->shapes; shape != NULL; shape = shape->next) {
103		if (shape->fill.type != NSVG_PAINT_NONE)
104			_AddShape(shape, false, transform, icon);
105		if (shape->stroke.type != NSVG_PAINT_NONE)
106			_AddShape(shape, true, transform, icon);
107	}
108
109	// clean up styles and paths (remove duplicates)
110	int32 count = icon->Shapes()->CountItems();
111	for (int32 i = 1; i < count; i++) {
112		PathSourceShape* shape = dynamic_cast<PathSourceShape*>(icon->Shapes()->ItemAtFast(i));
113		if (shape == NULL)
114			continue;
115
116		Style* style = shape->Style();
117		if (style == NULL)
118			continue;
119		int32 styleIndex = icon->Styles()->IndexOf(style);
120		for (int32 j = 0; j < styleIndex; j++) {
121			Style* earlierStyle = icon->Styles()->ItemAtFast(j);
122			if (*style == *earlierStyle) {
123				shape->SetStyle(earlierStyle);
124				icon->Styles()->RemoveItem(style);
125				style->ReleaseReference();
126				break;
127			}
128		}
129	}
130
131	return B_OK;
132}
133
134
135// AddVertexSource
136status_t
137AddPathsFromVertexSource(Icon* icon, PathSourceShape* shape, NSVGshape* svgShape)
138{
139//printf("AddPathsFromVertexSource(pathID = %ld)\n", index);
140
141	for (NSVGpath* svgPath = svgShape->paths; svgPath != NULL;
142		svgPath = svgPath->next) {
143		VectorPath* path = new (nothrow) VectorPath();
144		if (!path || !icon->Paths()->AddItem(path)) {
145			delete path;
146			return B_NO_MEMORY;
147		}
148
149		if (!shape->Paths()->AddItem(path))
150			return B_NO_MEMORY;
151
152		path->SetClosed(svgPath->closed);
153
154		int pointCount = svgPath->npts;
155		float* points = svgPath->pts;
156
157		// First entry in the points list is always a "move" to the path
158		// starting point
159		if (!path->AddPoint(BPoint(points[0], points[1])))
160			return B_NO_MEMORY;
161		path->SetInOutConnected(path->CountPoints() - 1, false);
162
163		pointCount--;
164		points += 2;
165
166		while (pointCount > 0) {
167			BPoint vector1(points[0], points[1]);
168			BPoint vector2(points[2], points[3]);
169			BPoint endPoint(points[4], points[5]);
170
171			if (!path->AddPoint(endPoint))
172				return B_NO_MEMORY;
173
174			int32 start = path->CountPoints() - 2;
175			int32 end = path->CountPoints() - 1;
176
177			path->SetInOutConnected(end, false);
178			path->SetPointOut(start, vector1);
179			path->SetPointIn(end, vector2);
180
181			pointCount -= 3;
182			points += 6;
183		}
184	}
185
186	// FIXME handle closed vs open paths
187
188	return B_OK;
189}
190
191
192// _AddShape
193status_t
194DocumentBuilder::_AddShape(NSVGshape* svgShape, bool outline,
195						   const Transformable& transform, Icon* icon)
196{
197	PathSourceShape* shape = new (nothrow) PathSourceShape(NULL);
198	if (!shape || !icon->Shapes()->AddItem(shape)) {
199		delete shape;
200		return B_NO_MEMORY;
201	}
202
203	if (AddPathsFromVertexSource(icon, shape, svgShape) < B_OK)
204		printf("failed to convert from vertex source\n");
205
206	shape->SetName(svgShape->id);
207	shape->Multiply(transform);
208
209	StrokeTransformer* stroke = NULL;
210	NSVGpaint* paint = NULL;
211	if (outline) {
212		stroke = new (nothrow) StrokeTransformer(shape->VertexSource());
213		paint = &svgShape->stroke;
214
215		if (stroke) {
216			stroke->width(svgShape->strokeWidth);
217			switch(svgShape->strokeLineCap) {
218				case NSVG_CAP_BUTT:
219					stroke->line_cap(agg::butt_cap);
220					break;
221				case NSVG_CAP_ROUND:
222					stroke->line_cap(agg::round_cap);
223					break;
224				case NSVG_CAP_SQUARE:
225					stroke->line_cap(agg::square_cap);
226					break;
227			}
228
229			switch(svgShape->strokeLineJoin) {
230				case NSVG_JOIN_MITER:
231					stroke->line_join(agg::miter_join);
232					break;
233				case NSVG_JOIN_ROUND:
234					stroke->line_join(agg::round_join);
235					break;
236				case NSVG_JOIN_BEVEL:
237					stroke->line_join(agg::bevel_join);
238					break;
239			}
240		}
241
242		if (!shape->Transformers()->AddItem(stroke)) {
243			delete stroke;
244			stroke = NULL;
245		}
246	} else {
247		paint = &svgShape->fill;
248#if 0 // FIXME filling rule are missing from PathSourceShape class
249		if (svgShape->fillRule == NSVG_FILLRULE_EVENODD)
250			shape->SetFillingRule(FILL_MODE_EVEN_ODD);
251		else
252			shape->SetFillingRule(FILL_MODE_NON_ZERO);
253#endif
254	}
255
256	Gradient gradient(true);
257	rgb_color color;
258	switch(paint->type) {
259		case NSVG_PAINT_COLOR:
260			color.red = paint->color & 0xFF;
261			color.green = (paint->color >> 8) & 0xFF;
262			color.blue = (paint->color >> 16) & 0xFF;
263			color.alpha = (paint->color >> 24) & 0xFF;
264			break;
265		case NSVG_PAINT_LINEAR_GRADIENT:
266		{
267			gradient.SetType(GRADIENT_LINEAR);
268			// The base gradient axis in Icon-O-Matic is a horizontal line from
269			// (-64, 0) to (64, 0). The base gradient axis used by nanosvg is
270			// a vertical line from (0, 0) to (0, 1). This initial transform
271			// converts from one space to the other.
272			agg::trans_affine baseTransform(0, 1.0/128.0, -1.0/128.0, 0,
273				-0.5, 0.5);
274			gradient.multiply(baseTransform);
275			break;
276		}
277		case NSVG_PAINT_RADIAL_GRADIENT:
278		{
279			gradient.SetType(GRADIENT_CIRCULAR);
280			agg::trans_affine baseTransform(0, 1.0/64.0, -1.0/64.0, 0,
281				0, 0);
282			gradient.multiply(baseTransform);
283			break;
284		}
285	}
286
287	if (paint->type != NSVG_PAINT_COLOR) {
288		gradient.SetInterpolation(INTERPOLATION_LINEAR);
289		agg::trans_affine gradientTransform(
290			paint->gradient->xform[0], paint->gradient->xform[1],
291			paint->gradient->xform[2], paint->gradient->xform[3],
292			paint->gradient->xform[4], paint->gradient->xform[5]
293		);
294
295		// The transform from nanosvg converts a screen space coordinate into
296		// a gradient offset. It is the inverse of what we need at this stage,
297		// so we just invert it and multiply our base transform with it.
298		gradient.multiply_inv(gradientTransform);
299
300		// Finally, scale the gradient according to the global scaling to fit
301		// the 64x64 box we work in.
302		gradient.Multiply(*shape);
303
304		for (int i = 0; i < paint->gradient->nstops; i++) {
305			rgb_color stopColor;
306			stopColor.red = paint->gradient->stops[i].color & 0xFF;
307			stopColor.green = (paint->gradient->stops[i].color >> 8) & 0xFF;
308			stopColor.blue = (paint->gradient->stops[i].color >> 16) & 0xFF;
309			stopColor.alpha = (paint->gradient->stops[i].color >> 24) & 0xFF;
310			gradient.AddColor(stopColor, paint->gradient->stops[i].offset);
311		}
312
313		BGradient::ColorStop* step = gradient.ColorAt(0);
314		if (step) {
315			color.red		= step->color.red;
316			color.green		= step->color.green;
317			color.blue		= step->color.blue;
318			color.alpha		= step->color.alpha;
319		}
320	}
321
322	color.alpha = (uint8)(color.alpha * svgShape->opacity);
323
324	Style* style = new (nothrow) Style(color);
325	if (!style || !icon->Styles()->AddItem(style)) {
326		delete style;
327		return B_NO_MEMORY;
328	}
329
330	// NOTE: quick hack to freeze all transformations (only works because
331	// paths and styles are not used by multiple shapes!!)
332	int32 pathCount = shape->Paths()->CountItems();
333	for (int32 i = 0; i < pathCount; i++) {
334		VectorPath* path = shape->Paths()->ItemAtFast(i);
335		path->ApplyTransform(*shape);
336	}
337
338	if (stroke)
339		stroke->width(stroke->width() * shape->scale());
340
341	if (paint->type != NSVG_PAINT_COLOR)
342		style->SetGradient(&gradient);
343
344	shape->Reset();
345
346	shape->SetStyle(style);
347
348	return B_OK;
349}
350
351