xref: /haiku/src/apps/icon-o-matic/import_export/svg/DocumentBuilder.cpp (revision 13581b3d2a71545960b98fefebc5225b5bf29072)
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 
30 using std::nothrow;
31 
32 // constructor
33 DocumentBuilder::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
44 void
45 DocumentBuilder::SetTitle(const char* title)
46 {
47 	fTitle = title;
48 }
49 
50 // SetDimensions
51 void
52 DocumentBuilder::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
62 status_t
63 DocumentBuilder::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;
73 printf("view box: ");
74 bounds.PrintToStream();
75 	} else {
76 		bounds.Set(0.0, 0.0, (int32)fWidth - 1, (int32)fHeight - 1);
77 printf("width/height: ");
78 bounds.PrintToStream();
79 	}
80 
81 	BRect boundingBox(xMin, yMin, xMax, yMax);
82 
83 	if (!bounds.IsValid() || !boundingBox.Intersects(bounds)) {
84 		bounds = boundingBox;
85 printf("using bounding box: ");
86 bounds.PrintToStream();
87 	}
88 
89 	float size = max_c(bounds.Width(), bounds.Height());
90 	double scale = 64.0 / size;
91 printf("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
136 status_t
137 AddPathsFromVertexSource(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
193 status_t
194 DocumentBuilder::_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