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