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