c4fb41cecec89c4c350e8e68c16dd81cfb144052
1 #define SP_CANVAS_AXONOMGRID_C\r
2 \r
3 /*\r
4 * SPCAxonomGrid\r
5 *\r
6 * Copyright (C) 2006 Johan Engelen <johan@shouraizou.nl>\r
7 * Copyright (C) 2000 Lauris Kaplinski\r
8 *\r
9 */ \r
10 \r
11 /* \r
12 * Current limits are: one axis (y-axis) is always vertical. The other two\r
13 * axes are bound to a certain range of angles. The z-axis always has an angle \r
14 * smaller than 90 degrees (measured from horizontal, 0 degrees being a line extending\r
15 * to the right). The x-axis will always have an angle between 0 and 90 degrees.\r
16 * When I quickly think about it: all possibilities are probably covered this way. Eg.\r
17 * a z-axis with negative angle can be replaced with an x-axis, etc.\r
18 */ \r
19 \r
20 /*\r
21 * TODO: LOTS LOTS LOTS. Optimization etc.\r
22 *\r
23 */\r
24 \r
25 #include "sp-canvas-util.h"\r
26 #include "canvas-axonomgrid.h"\r
27 #include "display-forward.h"\r
28 #include <libnr/nr-pixops.h>\r
29 \r
30 #define SAFE_SETPIXEL //undefine this when it is certain that setpixel is never called with invalid params\r
31 \r
32 enum {\r
33 ARG_0,\r
34 ARG_ORIGINX,\r
35 ARG_ORIGINY,\r
36 ARG_ANGLEX,\r
37 ARG_SPACINGY,\r
38 ARG_ANGLEZ,\r
39 ARG_COLOR,\r
40 ARG_EMPCOLOR,\r
41 ARG_EMPSPACING\r
42 };\r
43 \r
44 enum Dim3 { X=0, Y, Z };\r
45 \r
46 #ifndef M_PI\r
47 #define M_PI 3.14159265358979323846\r
48 #endif\r
49 \r
50 static double deg_to_rad(double deg) { return deg*M_PI/180.0;}\r
51 \r
52 \r
53 static void sp_caxonomgrid_class_init (SPCAxonomGridClass *klass);\r
54 static void sp_caxonomgrid_init (SPCAxonomGrid *grid);\r
55 static void sp_caxonomgrid_destroy (GtkObject *object);\r
56 static void sp_caxonomgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id);\r
57 \r
58 static void sp_caxonomgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags);\r
59 static void sp_caxonomgrid_render (SPCanvasItem *item, SPCanvasBuf *buf);\r
60 \r
61 static SPCanvasItemClass * parent_class;\r
62 \r
63 GtkType\r
64 sp_caxonomgrid_get_type (void)\r
65 {\r
66 static GtkType caxonomgrid_type = 0;\r
67 \r
68 if (!caxonomgrid_type) {\r
69 GtkTypeInfo caxonomgrid_info = {\r
70 "SPCAxonomGrid",\r
71 sizeof (SPCAxonomGrid),\r
72 sizeof (SPCAxonomGridClass),\r
73 (GtkClassInitFunc) sp_caxonomgrid_class_init,\r
74 (GtkObjectInitFunc) sp_caxonomgrid_init,\r
75 NULL, NULL,\r
76 (GtkClassInitFunc) NULL\r
77 };\r
78 caxonomgrid_type = gtk_type_unique (sp_canvas_item_get_type (), &caxonomgrid_info);\r
79 }\r
80 return caxonomgrid_type;\r
81 }\r
82 \r
83 static void\r
84 sp_caxonomgrid_class_init (SPCAxonomGridClass *klass)\r
85 {\r
86 \r
87 GtkObjectClass *object_class;\r
88 SPCanvasItemClass *item_class;\r
89 \r
90 object_class = (GtkObjectClass *) klass;\r
91 item_class = (SPCanvasItemClass *) klass;\r
92 \r
93 parent_class = (SPCanvasItemClass*)gtk_type_class (sp_canvas_item_get_type ());\r
94 \r
95 gtk_object_add_arg_type ("SPCAxonomGrid::originx", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINX);\r
96 gtk_object_add_arg_type ("SPCAxonomGrid::originy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ORIGINY);\r
97 gtk_object_add_arg_type ("SPCAxonomGrid::anglex", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ANGLEX);\r
98 gtk_object_add_arg_type ("SPCAxonomGrid::spacingy", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_SPACINGY);\r
99 gtk_object_add_arg_type ("SPCAxonomGrid::anglez", GTK_TYPE_DOUBLE, GTK_ARG_WRITABLE, ARG_ANGLEZ);\r
100 gtk_object_add_arg_type ("SPCAxonomGrid::color", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_COLOR);\r
101 gtk_object_add_arg_type ("SPCAxonomGrid::empcolor", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPCOLOR);\r
102 gtk_object_add_arg_type ("SPCAxonomGrid::empspacing", GTK_TYPE_INT, GTK_ARG_WRITABLE, ARG_EMPSPACING);\r
103 \r
104 object_class->destroy = sp_caxonomgrid_destroy;\r
105 object_class->set_arg = sp_caxonomgrid_set_arg;\r
106 \r
107 item_class->update = sp_caxonomgrid_update;\r
108 item_class->render = sp_caxonomgrid_render;\r
109 \r
110 }\r
111 \r
112 static void\r
113 sp_caxonomgrid_init (SPCAxonomGrid *grid)\r
114 {\r
115 grid->origin[NR::X] = grid->origin[NR::Y] = 0.0;\r
116 // grid->spacing[X] = grid->spacing[Y] = grid->spacing[Z] = 8.0;\r
117 grid->color = 0x0000ff7f;\r
118 grid->empcolor = 0x3F3FFF40;\r
119 grid->empspacing = 5;\r
120 }\r
121 \r
122 static void\r
123 sp_caxonomgrid_destroy (GtkObject *object)\r
124 {\r
125 g_return_if_fail (object != NULL);\r
126 g_return_if_fail (SP_IS_CAXONOMGRID (object));\r
127 \r
128 if (GTK_OBJECT_CLASS (parent_class)->destroy)\r
129 (* GTK_OBJECT_CLASS (parent_class)->destroy) (object);\r
130 }\r
131 \r
132 static void\r
133 sp_caxonomgrid_set_arg (GtkObject *object, GtkArg *arg, guint arg_id)\r
134 {\r
135 SPCanvasItem *item = SP_CANVAS_ITEM (object);\r
136 SPCAxonomGrid *grid = SP_CAXONOMGRID (object);\r
137 \r
138 g_message("arg");\r
139 \r
140 switch (arg_id) {\r
141 case ARG_ORIGINX:\r
142 grid->origin[NR::X] = GTK_VALUE_DOUBLE (* arg);\r
143 sp_canvas_item_request_update (item);\r
144 break;\r
145 case ARG_ORIGINY:\r
146 grid->origin[NR::Y] = GTK_VALUE_DOUBLE (* arg);\r
147 sp_canvas_item_request_update (item);\r
148 break;\r
149 case ARG_ANGLEX:\r
150 grid->angle_deg[X] = GTK_VALUE_DOUBLE (* arg);\r
151 if (grid->angle_deg[X] < 0.0) grid->angle_deg[X] = 0.0;\r
152 if (grid->angle_deg[X] > 89.0) grid->angle_deg[X] = 89.0;\r
153 grid->angle_rad[X] = deg_to_rad(grid->angle_deg[X]);\r
154 grid->tan_angle[X] = tan(grid->angle_rad[X]);\r
155 sp_canvas_item_request_update (item);\r
156 break;\r
157 case ARG_SPACINGY:\r
158 grid->lengthy = GTK_VALUE_DOUBLE (* arg);\r
159 if (grid->lengthy < 0.01) grid->lengthy = 0.01;\r
160 sp_canvas_item_request_update (item);\r
161 break;\r
162 case ARG_ANGLEZ:\r
163 grid->angle_deg[Z] = GTK_VALUE_DOUBLE (* arg);\r
164 if (grid->angle_deg[Z] < 0.0) grid->angle_deg[Z] = 0.0;\r
165 if (grid->angle_deg[X] > 89.0) grid->angle_deg[X] = 89.0;\r
166 grid->angle_rad[Z] = deg_to_rad(grid->angle_deg[Z]);\r
167 grid->tan_angle[Z] = tan(grid->angle_rad[Z]);\r
168 sp_canvas_item_request_update (item);\r
169 break;\r
170 case ARG_COLOR:\r
171 grid->color = GTK_VALUE_INT (* arg);\r
172 sp_canvas_item_request_update (item);\r
173 break;\r
174 case ARG_EMPCOLOR:\r
175 grid->empcolor = GTK_VALUE_INT (* arg);\r
176 sp_canvas_item_request_update (item);\r
177 break;\r
178 case ARG_EMPSPACING:\r
179 grid->empspacing = GTK_VALUE_INT (* arg);\r
180 // std::cout << "Emphasis Spacing: " << grid->empspacing << std::endl;\r
181 sp_canvas_item_request_update (item);\r
182 break;\r
183 default:\r
184 break;\r
185 }\r
186 }\r
187 \r
188 \r
189 \r
190 /**\r
191 \brief This function renders a pixel on a particular buffer.\r
192 \r
193 The topleft of the buffer equals\r
194 ( rect.x0 , rect.y0 ) in screen coordinates\r
195 ( 0 , 0 ) in setpixel coordinates\r
196 The bottomright of the buffer equals\r
197 ( rect.x1 , rect,y1 ) in screen coordinates\r
198 ( rect.x1 - rect.x0 , rect.y1 - rect.y0 ) in setpixel coordinates\r
199 */\r
200 static void \r
201 sp_caxonomgrid_setpixel (SPCanvasBuf *buf, gint x, gint y, guint32 rgba) {\r
202 #ifdef SAFE_SETPIXEL\r
203 if ( (x >= buf->rect.x0) && (x < buf->rect.x1) && (y >= buf->rect.y0) && (y < buf->rect.y1) ) {\r
204 #endif \r
205 guint r, g, b, a; \r
206 r = NR_RGBA32_R (rgba);\r
207 g = NR_RGBA32_G (rgba);\r
208 b = NR_RGBA32_B (rgba);\r
209 a = NR_RGBA32_A (rgba); \r
210 guchar * p = buf->buf + (y - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;\r
211 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);\r
212 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);\r
213 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);\r
214 #ifdef SAFE_SETPIXEL\r
215 }\r
216 #endif \r
217 }\r
218 \r
219 /**\r
220 \brief This function renders a line on a particular canvas buffer,\r
221 using Bresenham's line drawing function.\r
222 http://www.cs.unc.edu/~mcmillan/comp136/Lecture6/Lines.html \r
223 Coordinates are interpreted as SCREENcoordinates\r
224 */\r
225 static void \r
226 sp_caxonomgrid_drawline (SPCanvasBuf *buf, gint x0, gint y0, gint x1, gint y1, guint32 rgba) {\r
227 int dy = y1 - y0;\r
228 int dx = x1 - x0;\r
229 int stepx, stepy;\r
230 \r
231 if (dy < 0) { dy = -dy; stepy = -1; } else { stepy = 1; }\r
232 if (dx < 0) { dx = -dx; stepx = -1; } else { stepx = 1; }\r
233 dy <<= 1; // dy is now 2*dy\r
234 dx <<= 1; // dx is now 2*dx\r
235 \r
236 sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
237 if (dx > dy) {\r
238 int fraction = dy - (dx >> 1); // same as 2*dy - dx\r
239 while (x0 != x1) {\r
240 if (fraction >= 0) {\r
241 y0 += stepy;\r
242 fraction -= dx; // same as fraction -= 2*dx\r
243 }\r
244 x0 += stepx;\r
245 fraction += dy; // same as fraction -= 2*dy\r
246 sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
247 }\r
248 } else {\r
249 int fraction = dx - (dy >> 1);\r
250 while (y0 != y1) {\r
251 if (fraction >= 0) {\r
252 x0 += stepx;\r
253 fraction -= dy;\r
254 }\r
255 y0 += stepy;\r
256 fraction += dx;\r
257 sp_caxonomgrid_setpixel(buf, x0, y0, rgba);\r
258 }\r
259 }\r
260 \r
261 }\r
262 \r
263 static void\r
264 sp_grid_vline (SPCanvasBuf *buf, gint x, gint ys, gint ye, guint32 rgba)\r
265 {\r
266 if ((x >= buf->rect.x0) && (x < buf->rect.x1)) {\r
267 guint r, g, b, a;\r
268 gint y0, y1, y;\r
269 guchar *p;\r
270 r = NR_RGBA32_R(rgba);\r
271 g = NR_RGBA32_G (rgba);\r
272 b = NR_RGBA32_B (rgba);\r
273 a = NR_RGBA32_A (rgba);\r
274 y0 = MAX (buf->rect.y0, ys);\r
275 y1 = MIN (buf->rect.y1, ye + 1);\r
276 p = buf->buf + (y0 - buf->rect.y0) * buf->buf_rowstride + (x - buf->rect.x0) * 3;\r
277 for (y = y0; y < y1; y++) {\r
278 p[0] = NR_COMPOSEN11_1111 (r, a, p[0]);\r
279 p[1] = NR_COMPOSEN11_1111 (g, a, p[1]);\r
280 p[2] = NR_COMPOSEN11_1111 (b, a, p[2]);\r
281 p += buf->buf_rowstride;\r
282 }\r
283 }\r
284 }\r
285 \r
286 /**\r
287 \brief This function renders the grid on a particular canvas buffer\r
288 \param item The grid to render on the buffer\r
289 \param buf The buffer to render the grid on\r
290 \r
291 This function gets called a touch more than you might believe,\r
292 about once per tile. This means that it could probably be optimized\r
293 and help things out.\r
294 \r
295 Basically this function has to determine where in the canvas it is,\r
296 and how that associates with the grid. It does this first by looking\r
297 at the bounding box of the buffer, and then calculates where the grid\r
298 starts in that buffer. It will then step through grid lines until\r
299 it is outside of the buffer.\r
300 \r
301 For each grid line it is drawn using the function \c sp_grid_hline\r
302 or \c sp_grid_vline. These are convience functions for the sake\r
303 of making the function easier to read.\r
304 \r
305 Also, there are emphasized lines on the grid. While the \c syg and\r
306 \c sxg variable track grid positioning, the \c xlinestart and \c\r
307 ylinestart variables track the 'count' of what lines they are. If\r
308 that count is a multiple of the line seperation between emphasis\r
309 lines, then that line is drawn in the emphasis color.\r
310 */\r
311 static void\r
312 sp_caxonomgrid_render (SPCanvasItem * item, SPCanvasBuf * buf)\r
313 {\r
314 SPCAxonomGrid *grid = SP_CAXONOMGRID (item);\r
315 \r
316 sp_canvas_prepare_buffer (buf);\r
317 \r
318 // gc = gridcoordinates (the coordinates calculated from the grids origin 'grid->ow'.\r
319 // sc = screencoordinates ( for example "buf->rect.x0" is in screencoordinates )\r
320 // bc = buffer patch coordinates \r
321 \r
322 // tl = topleft ; br = bottomright\r
323 NR::Point buf_tl_gc;\r
324 NR::Point buf_br_gc;\r
325 buf_tl_gc[NR::X] = buf->rect.x0 - grid->ow[NR::X];\r
326 buf_tl_gc[NR::Y] = buf->rect.y0 - grid->ow[NR::Y];\r
327 buf_br_gc[NR::X] = buf->rect.x1 - grid->ow[NR::X];\r
328 buf_br_gc[NR::Y] = buf->rect.y1 - grid->ow[NR::Y];\r
329 \r
330 \r
331 gdouble x;\r
332 gdouble y;\r
333 \r
334 // render the three separate line groups representing the main-axes:\r
335 // x-axis always goes from topleft to bottomright. (0,0) - (1,1) \r
336 const gdouble xintercept_y_bc = (buf_tl_gc[NR::X] * grid->tan_angle[X]) - buf_tl_gc[NR::Y] ;\r
337 const gdouble xstart_y_sc = ( xintercept_y_bc - floor(xintercept_y_bc/grid->lyw)*grid->lyw ) + buf->rect.y0;\r
338 const gint xlinestart = (gint) Inkscape::round( (xstart_y_sc - grid->ow[NR::Y]) / grid->lyw );\r
339 gint xlinenum;\r
340 // lijnen vanaf linker zijkant.\r
341 for (y = xstart_y_sc, xlinenum = xlinestart; y < buf->rect.y1; y += grid->lyw, xlinenum++) {\r
342 const gint x0 = buf->rect.x0;\r
343 const gint y0 = (gint) Inkscape::round(y);\r
344 const gint x1 = x0 + (gint) Inkscape::round( (buf->rect.y1 - y) / grid->tan_angle[X] );\r
345 const gint y1 = buf->rect.y1;\r
346 \r
347 if (!grid->scaled && (xlinenum % grid->empspacing) == 0) {\r
348 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
349 } else {\r
350 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
351 }\r
352 }\r
353 // lijnen vanaf bovenkant.\r
354 const gdouble xstart_x_sc = buf->rect.x0 + (grid->lxw_x - (xstart_y_sc - buf->rect.y0) / grid->tan_angle[X]) ;\r
355 for (x = xstart_x_sc, xlinenum = xlinestart; x < buf->rect.x1; x += grid->lxw_x, xlinenum--) {\r
356 const gint y0 = buf->rect.y0;\r
357 const gint y1 = buf->rect.y1;\r
358 const gint x0 = (gint) Inkscape::round(x);\r
359 const gint x1 = x0 + (gint) Inkscape::round( (y1 - y0) / grid->tan_angle[X] );\r
360 \r
361 if (!grid->scaled && (xlinenum % grid->empspacing) == 0) {\r
362 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
363 } else {\r
364 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
365 }\r
366 }\r
367 \r
368 \r
369 // y-axis lines (vertical)\r
370 const gdouble ystart_x_sc = floor (buf_tl_gc[NR::X] / grid->spacing_ylines) * grid->spacing_ylines + grid->ow[NR::X];\r
371 const gint ylinestart = (gint) Inkscape::round((ystart_x_sc - grid->ow[NR::X]) / grid->spacing_ylines);\r
372 gint ylinenum;\r
373 for (x = ystart_x_sc, ylinenum = ylinestart; x < buf->rect.x1; x += grid->spacing_ylines, ylinenum++) {\r
374 const gint x0 = (gint) Inkscape::round(x);\r
375 \r
376 if (!grid->scaled && (ylinenum % grid->empspacing) == 0) {\r
377 sp_grid_vline (buf, x0, buf->rect.y0, buf->rect.y1 - 1, grid->empcolor);\r
378 } else {\r
379 sp_grid_vline (buf, x0, buf->rect.y0, buf->rect.y1 - 1, grid->color);\r
380 }\r
381 }\r
382 \r
383 // z-axis always goes from bottomleft to topright. (0,1) - (1,0) \r
384 const gdouble zintercept_y_bc = (buf_tl_gc[NR::X] * -grid->tan_angle[Z]) - buf_tl_gc[NR::Y] ;\r
385 const gdouble zstart_y_sc = ( zintercept_y_bc - floor(zintercept_y_bc/grid->lyw)*grid->lyw ) + buf->rect.y0;\r
386 const gint zlinestart = (gint) Inkscape::round( (zstart_y_sc - grid->ow[NR::Y]) / grid->lyw );\r
387 gint zlinenum;\r
388 // lijnen vanaf linker zijkant.\r
389 for (y = zstart_y_sc, zlinenum = zlinestart; y < buf->rect.y1; y += grid->lyw, zlinenum++) {\r
390 const gint x0 = buf->rect.x0;\r
391 const gint y0 = (gint) Inkscape::round(y);\r
392 const gint x1 = x0 + (gint) Inkscape::round( (y - buf->rect.y0 ) / grid->tan_angle[Z] );\r
393 const gint y1 = buf->rect.y0;\r
394 \r
395 if (!grid->scaled && (zlinenum % grid->empspacing) == 0) {\r
396 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
397 } else {\r
398 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
399 }\r
400 }\r
401 // lijnen vanaf onderkant.\r
402 const gdouble zstart_x_sc = buf->rect.x0 + (y - buf->rect.y1) / grid->tan_angle[Z] ;\r
403 for (x = zstart_x_sc; x < buf->rect.x1; x += grid->lxw_z, zlinenum--) {\r
404 const gint y0 = buf->rect.y1;\r
405 const gint y1 = buf->rect.y0;\r
406 const gint x0 = (gint) Inkscape::round(x);\r
407 const gint x1 = x0 + (gint) Inkscape::round( (buf->rect.y1 - buf->rect.y0) / grid->tan_angle[Z] );\r
408 \r
409 if (!grid->scaled && (zlinenum % grid->empspacing) == 0) {\r
410 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->empcolor);\r
411 } else {\r
412 sp_caxonomgrid_drawline (buf, x0, y0, x1, y1, grid->color);\r
413 }\r
414 }\r
415 \r
416 }\r
417 \r
418 static void\r
419 sp_caxonomgrid_update (SPCanvasItem *item, NR::Matrix const &affine, unsigned int flags)\r
420 {\r
421 SPCAxonomGrid *grid = SP_CAXONOMGRID (item);\r
422 \r
423 if (parent_class->update)\r
424 (* parent_class->update) (item, affine, flags);\r
425 \r
426 grid->ow = grid->origin * affine;\r
427 grid->sw = NR::Point(fabs(affine[0]),fabs(affine[3]));\r
428 \r
429 for(int dim = 0; dim < 2; dim++) {\r
430 gint scaling_factor = grid->empspacing;\r
431 \r
432 if (scaling_factor <= 1)\r
433 scaling_factor = 5;\r
434 \r
435 grid->scaled = FALSE;\r
436 while (grid->sw[dim] < 8.0) {\r
437 grid->scaled = TRUE;\r
438 grid->sw[dim] *= scaling_factor;\r
439 // First pass, go up to the major line spacing, then\r
440 // keep increasing by two.\r
441 scaling_factor = 2;\r
442 }\r
443 }\r
444 \r
445 grid->spacing_ylines = grid->sw[NR::X] * grid->lengthy /(grid->tan_angle[X] + grid->tan_angle[Z]);\r
446 grid->lyw = grid->lengthy * grid->sw[NR::Y];\r
447 grid->lxw_x = (grid->lengthy / grid->tan_angle[X]) * grid->sw[NR::X];\r
448 grid->lxw_z = (grid->lengthy / grid->tan_angle[Z]) * grid->sw[NR::X];\r
449 \r
450 if (grid->empspacing == 0) {\r
451 grid->scaled = TRUE;\r
452 }\r
453 \r
454 sp_canvas_request_redraw (item->canvas,\r
455 -1000000, -1000000,\r
456 1000000, 1000000);\r
457 \r
458 item->x1 = item->y1 = -1000000;\r
459 item->x2 = item->y2 = 1000000;\r
460 }\r
461 \r
462 /*\r
463 Local Variables:\r
464 mode:c++\r
465 c-file-style:"stroustrup"\r
466 c-file-offsets:((innamespace . 0)(inline-open . 0)(case-label . +))\r
467 indent-tabs-mode:nil\r
468 fill-column:99\r
469 End:\r
470 */\r
471 // vim: filetype=cpp:expandtab:shiftwidth=4:tabstop=8:softtabstop=4 :\r