1
#include <string.h>
2
#include <stdlib.h>
3
#include <cairo.h>
4
#include <ccss-cairo/ccss-cairo.h>
5
#include <gtk/gtk.h>
6
7
/* ================================================================ */
8
/* Our little hierarchy */
9
typedef enum _CopperClasses {
10
  CC_FRAME,
11
  CC_CONTENT, CC_TITLEBAR,
12
  CC_MENU, CC_TITLE, CC_MINIMIZE, CC_MAXIMIZE, CC_CLOSE,
13
  CC_FILLER,
14
  CC_LAST
15
} CopperClasses;
16
17
char *names[] =
18
  {
19
    "frame",
20
    "area", "area",
21
    "button", "title", "button", "button", "button",
22
    "area",
23
    "last"
24
  };
25
26
CopperClasses parents[] =
27
  {
28
    CC_LAST,
29
    CC_FRAME, CC_FRAME,
30
    CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR, CC_TITLEBAR,
31
    CC_TITLEBAR,
32
    CC_LAST
33
  };
34
35
char *copper_classnames[] =
36
  {
37
    NULL,
38
    "content", "titlebar",
39
    "menu", NULL, "minimize", "maximize", "close",
40
    "filler",
41
    NULL
42
  };
43
44
typedef struct
45
{
46
  ccss_node_t basic;
47
  CopperClasses copper_class;
48
} CopperNode;
49
50
CopperNode copper_nodes[CC_LAST];
51
52
static char const*
53
get_type (ccss_node_t const *self)
54
{
55
  g_warning ("It's %s", self->type_name);
56
  return self->type_name;
57
}
58
59
static ptrdiff_t
60
get_instance (ccss_node_t const *self)
61
{
62
  return self->instance;
63
}
64
65
static char const*
66
get_style (ccss_node_t const *self) {
67
  return self->inline_style;
68
}
69
70
static ccss_node_t*
71
get_container (ccss_node_t const *self)
72
{
73
  CopperClasses candidate = parents[((CopperNode*)self)->copper_class];
74
75
  if (candidate==CC_LAST)
76
    return NULL;
77
  else
78
    /* or should we allocate a new one? */
79
    return (ccss_node_t*) &(copper_nodes[candidate]);
80
}
81
82
static const char*
83
get_class (ccss_node_t const *self)
84
{
85
  return copper_classnames[((CopperNode*)self)->copper_class];
86
}
87
88
static ccss_node_class_t copper_node_class = {
89
  .get_type               = (ccss_node_get_type_f) get_type,
90
  .get_instance           = (ccss_node_get_instance_f) get_instance,
91
  .get_style              = (ccss_node_get_style_f) get_style,
92
  .get_container          = (ccss_node_get_container_f) get_container,
93
  .get_class              = (ccss_node_get_class_f) get_class
94
};
95
96
static void
97
initialise_classes (void)
98
{
99
  int i;
100
101
  for (i=0; i<CC_LAST; i++)
102
    {
103
      ccss_node_init ((ccss_node_t*) &copper_nodes[i], &copper_node_class);
104
      copper_nodes[i].basic.type_name = names[i];
105
      copper_nodes[i].basic.instance = 0;
106
      copper_nodes[i].basic.inline_style = NULL;
107
      copper_nodes[i].copper_class = i;
108
    }
109
}
110
111
/* ================================================================ */
112
113
static gboolean
114
get_number_from_style (ccss_style_t *style,
115
		       char *element,
116
		       int *dummy,
117
		       int *original_value)
118
{
119
  double d = 0.0;
120
  gboolean result;
121
122
  result = ccss_style_get_double (style, element, &d);
123
124
  if (original_value)
125
    *original_value = d;
126
127
  return result;
128
}
129
130
static void
131
reduce_by_padding_borders_and_margins (ccss_stylesheet_t *stylesheet,
132
				       CopperClasses style_id,
133
				       int *x, int *y, int *w, int *h,
134
				       gboolean honour_margins,
135
				       gboolean reverse)
136
{
137
  ccss_style_t *style = ccss_stylesheet_query (stylesheet,
138
					       (ccss_node_t*) &copper_nodes[style_id]);
139
  int bt=0, br=0, bb=0, bl=0, pt=0, pr=0, pb=0, pl=0;
140
141
  if (!style) return;
142
143
  /* FIXME this is silly; libccss should do this for us */
144
  /* FIXME maybe it does if we get the property rather than the number */
145
  if (get_number_from_style (style, "border-width",    NULL, &bl))
146
    {
147
      bt = br = bb = bl;
148
    }
149
  else
150
    {
151
      get_number_from_style (style, "border-top-width",     NULL, &bt);
152
      get_number_from_style (style, "border-right-width",   NULL, &br);
153
      get_number_from_style (style, "border-bottom-width",  NULL, &bb);
154
      get_number_from_style (style, "border-left-width",    NULL, &bl);
155
    }
156
157
  if (get_number_from_style (style, "padding",    NULL, &pl))
158
    {
159
      pt = pr = pb = pl;
160
    }
161
  else
162
    {
163
      get_number_from_style (style, "padding-top",          NULL, &pt);
164
      get_number_from_style (style, "padding-right",        NULL, &pr);
165
      get_number_from_style (style, "padding-bottom",       NULL, &pb);
166
      get_number_from_style (style, "padding-left",         NULL, &pl);
167
    }
168
169
  /* FIXME honour honour_margins */
170
171
  bt += pt;
172
  br += pr;
173
  bb += pb;
174
  bl += pl;
175
176
  if (reverse)
177
    {
178
      if (x) *x -= bl;
179
      if (y) *y -= bt;
180
      if (w) *w += (bl+br);
181
      if (h) *h += (bt+bb);
182
    }
183
  else
184
    {
185
      if (x) *x += bl;
186
      if (y) *y += bt;
187
      if (w) *w -= (bl+br);
188
      if (h) *h -= (bt+bb);
189
    }
190
191
  ccss_style_destroy (style);
192
}
193
194
static gint
195
draw_rectangle (ccss_stylesheet_t *stylesheet,
196
		cairo_t *cr,
197
		CopperClasses style_id,
198
		int x, int y, int w, int h,
199
		gboolean honour_margins,
200
		gboolean from_the_right,
201
		PangoLayout *layout)
202
{
203
  ccss_style_t *style = ccss_stylesheet_query (stylesheet,
204
					       (ccss_node_t*) &copper_nodes[style_id]);
205
  int full_width;
206
  int horizontal_margin = 0;
207
208
  if (!style) return 0;
209
210
  if (honour_margins)
211
    {
212
      int mn, mt, mr, mb, ml;
213
      /* FIXME: Setting just "margin" doesn't work
214
       * because libccss doesn't know about margins.
215
       */
216
      get_number_from_style (style, "margin",              NULL, &mn);
217
      if (mn)
218
	{
219
	  /* FIXME this is broken; "margin" may have multiple values */
220
	  mt = mr = mb = ml = mn;
221
	}
222
      else
223
	{
224
	  get_number_from_style (style, "margin-top",          NULL, &mt);
225
	  get_number_from_style (style, "margin-right",        NULL, &mr);
226
	  get_number_from_style (style, "margin-bottom",       NULL, &mb);
227
	  get_number_from_style (style, "margin-left",         NULL, &ml);
228
	}
229
230
      x += ml;
231
      y += mt;
232
      horizontal_margin = ml+mr;
233
      h -= (mt+mb);
234
    }
235
236
  if (w==0)
237
    {
238
      int height, width;
239
240
      get_number_from_style (style, "height", NULL, &height);
241
      get_number_from_style (style, "width",  NULL, &width);
242
243
      if (height!=0 && width!=0)
244
	{
245
	  int min_w, max_w;
246
	  double scale = ((double)h/(double)height);
247
248
	  w = (int) ((double)width) * scale;
249
250
	  get_number_from_style (style, "min-width", NULL, &min_w);
251
	  get_number_from_style (style, "max-width", NULL, &max_w);
252
253
	  if (max_w && w>max_w && max_w>min_w) w = max_w;
254
	  if (w<min_w) w = min_w;
255
	}
256
    }
257
258
  full_width = w+ horizontal_margin;
259
260
  if (from_the_right)
261
    x -= full_width;
262
263
  ccss_cairo_style_draw_rectangle (style, cr, x, y, w, h);
264
265
  if (layout)
266
    {
267
      reduce_by_padding_borders_and_margins (stylesheet, CC_TITLE,
268
					     &x, &y, &w, &h,
269
					     TRUE, FALSE);
270
      /*
271
       * we should maybe undo the translate later, but
272
       * we're always the last one to use this context anyway
273
       */
274
      cairo_translate (cr, x, y);
275
276
      pango_cairo_show_layout (cr, layout);
277
    }
278
279
  ccss_style_destroy (style);
280
281
  return full_width;
282
}
283
284
static char*
285
handle_data_url(char *data)
286
{
287
  const char* base64 = "base64";
288
  char *cursor = data;
289
  const char *base64_cursor = base64;
290
  gchar *result;
291
  gsize result_length;
292
  gchar *md5, *filename, *uri;
293
294
  md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, cursor, -1);
295
296
  /* FIXME: here we should check whether it already exists in /tmp
297
   * and return that if so
298
   */
299
300
  while (*cursor && *cursor!=',')
301
    {
302
      if (*base64_cursor)
303
	{
304
	  if (*cursor==*base64_cursor)
305
	    base64_cursor++;
306
	  else
307
	    base64_cursor = base64;
308
	}
309
      cursor++;
310
    }
311
312
  if (*base64_cursor)
313
    {
314
      /* we didn't see "base64" */
315
      g_warning ("data: URLs must be base64 encoded in this system!");
316
      g_free (md5);
317
      return NULL;
318
    }
319
320
  result = g_base64_decode (cursor+1, &result_length);
321
322
  /* unfortunately we can't just return the data; libccss requires it
323
   * to be in a file.
324
   */
325
  filename = g_build_filename ("/tmp", md5, NULL);
326
  g_file_set_contents (filename, result, result_length, NULL);
327
328
  uri = g_strdup_printf ("file://%s", filename);
329
  g_free (filename);
330
  g_free (result);
331
  g_free (md5);
332
333
  return uri;
334
}
335
336
static char *
337
url (GSList const	*args,
338
     void		*user_data)
339
{
340
  char *cwd;
341
  char *path;
342
  char *uri;
343
  char *filename = NULL;
344
  
345
  g_return_val_if_fail (args && args->data, NULL);
346
  filename = (char*) args->data;
347
348
  if (strcmp (filename, "wm:icon")==0)
349
    {
350
      /* this is magic */
351
      return g_strdup ("file:///service/website/themecreator.marnanel.org/htdocs/gtk-edit.png");
352
    }
353
#if 0
354
  else if (strncmp (filename, "data:", 5)==0)
355
    {
356
      return handle_data_url (filename+5);
357
    }
358
#endif
359
  else if (index (filename, '/') == NULL)
360
    {
361
      char *md5, *result;
362
      md5 = g_compute_checksum_for_string (G_CHECKSUM_MD5, filename, -1);
363
      result = g_strdup_printf ("file:///service/website/themecreator.marnanel.org/htdocs/w/images/%c/%c%c/%s",
364
				md5[0], md5[0], md5[1],
365
				filename);
366
      g_free (md5);
367
      return result;
368
    }
369
  else
370
    {
371
      return g_strdup ("file:///service/website/themecreator.marnanel.org/htdocs/gtk-error.png");
372
    }
373
}
374
375
static char *
376
named (GSList const *args, void *user_data)
377
{
378
  char *name = NULL;
379
380
  if (!name)
381
    /* fall back to black */
382
    return g_strdup("#00000000");
383
384
  name = args->data;
385
  g_warning ("Looking up named colour %s", name);
386
387
  /* stub: magenta for everything */
388
  return g_strdup("#FF00FFFF");
389
}
390
391
static ccss_function_t const _functions[] = 
392
{
393
  { "url",	url,	NULL },
394
  { "named",    named,  NULL },
395
  { NULL }
396
};
397
398
static PangoLayout*
399
title_text (ccss_stylesheet_t *stylesheet,
400
	    cairo_t *cr,
401
	    char *text)
402
{
403
  PangoLayout			 *layout;
404
  PangoAttrList *attrs = NULL;
405
  ccss_style_t *style = ccss_stylesheet_query (stylesheet,
406
					       (ccss_node_t*) &copper_nodes[CC_TITLE]);
407
  char *align;
408
  ccss_color_t const *colour;
409
410
  layout = pango_cairo_create_layout (cr);
411
412
  attrs = pango_attr_list_new ();
413
414
  /* We have to handle CSS text properties ourselves here because
415
   * libccss doesn't know how to render them yet.  We don't try
416
   * to do all the funky effects like shadows and so on,
417
   * unfortunately.
418
   */
419
420
  if (ccss_style_get_property (style, "color",
421
			       (const ccss_property_base_t**) &colour))
422
    {
423
      pango_attr_list_insert (attrs,
424
			      pango_attr_foreground_new
425
			      ((int)65535.0*colour->red,
426
			       (int)65535.0*colour->green,
427
			       (int)65535.0*colour->blue));
428
    }
429
  
430
  /* Alignment */
431
  
432
  if (ccss_style_get_string (style, "text-align", &align))
433
    {
434
      if (strcmp (align, "left")==0)
435
	{
436
	  pango_layout_set_alignment (layout, PANGO_ALIGN_LEFT);
437
	}
438
      else if (strcmp (align, "center")==0)
439
	{
440
	  pango_layout_set_alignment (layout, PANGO_ALIGN_CENTER);
441
	}
442
      else if (strcmp (align, "right")==0)
443
	{
444
	  pango_layout_set_alignment (layout, PANGO_ALIGN_RIGHT);
445
	}
446
      else
447
	/* FIXME: "align" contains garbage if it's undefined;
448
	 * is there an easy way of checking?
449
	 */
450
	g_warning ("Unknown alignment: %s", align);
451
    }
452
453
  pango_layout_set_attributes (layout, attrs);
454
455
  /* The actual text */
456
  pango_layout_set_text (layout, text, -1);
457
458
  ccss_style_destroy (style);
459
460
  return layout;
461
}
462
463
static void
464
render_copper (cairo_surface_t *surface,
465
	       ccss_stylesheet_t *stylesheet)
466
{
467
  cairo_t *cr = cairo_create (surface);
468
469
  int x = 0;
470
  int y = 0;
471
  int w = cairo_image_surface_get_width (surface);
472
  int h = cairo_image_surface_get_height (surface);
473
474
  PangoRectangle text_extents;
475
476
  int titlebar_height;
477
478
  CopperClasses left_buttons[] = {CC_MENU, CC_LAST};
479
  CopperClasses right_buttons[] = {CC_CLOSE, CC_MAXIMIZE, CC_MINIMIZE, CC_LAST};
480
  CopperClasses *cursor;
481
  int leftpos, rightpos;
482
483
  PangoLayout *layout = title_text (stylesheet, cr, "Badgers");
484
485
  pango_layout_get_pixel_extents (layout, NULL, &text_extents);
486
  reduce_by_padding_borders_and_margins (stylesheet, CC_TITLE,
487
					 &text_extents.x, &text_extents.y,
488
					 &text_extents.width,
489
					 &text_extents.height,
490
					 TRUE, TRUE);
491
492
  titlebar_height = text_extents.height;
493
494
  reduce_by_padding_borders_and_margins (stylesheet, CC_TITLEBAR,
495
					 NULL, NULL, NULL,
496
				         &titlebar_height,
497
					 TRUE, TRUE);
498
499
  draw_rectangle (stylesheet, cr, CC_FRAME, x, y, w, h, FALSE, FALSE, NULL);
500
501
  reduce_by_padding_borders_and_margins (stylesheet, CC_FRAME, &x, &y, &w, &h, FALSE, FALSE);
502
503
  draw_rectangle (stylesheet, cr, CC_TITLEBAR, x, y, w, titlebar_height, TRUE, FALSE, NULL);
504
505
  draw_rectangle (stylesheet, cr, CC_CONTENT,  x, y+titlebar_height, w, h-titlebar_height, TRUE, FALSE, NULL);
506
507
  h = titlebar_height;
508
509
  reduce_by_padding_borders_and_margins (stylesheet, CC_TITLEBAR, &x, &y, &w, &h, TRUE, FALSE);
510
511
  cursor = left_buttons;
512
  leftpos = x;
513
  while (*cursor != CC_LAST)
514
    {
515
      leftpos += draw_rectangle (stylesheet, cr, *cursor, leftpos, y, 0, h, TRUE, FALSE, NULL);
516
      cursor++;
517
    }
518
519
  cursor = right_buttons;
520
  rightpos = x+w;
521
  while (*cursor != CC_LAST)
522
    {
523
      rightpos -= draw_rectangle (stylesheet, cr, *cursor,    rightpos, y, 0, h, TRUE, TRUE, NULL);
524
      cursor++;
525
    }
526
527
  switch (pango_layout_get_alignment (layout))
528
    {
529
    case PANGO_ALIGN_LEFT:
530
      x = leftpos;
531
      draw_rectangle (stylesheet, cr, CC_FILLER, leftpos+text_extents.width, y,
532
		      (rightpos-leftpos)-text_extents.width, h, TRUE, FALSE, NULL);
533
      break;
534
    case PANGO_ALIGN_CENTER:
535
      x = leftpos + ((rightpos-leftpos)/2 - text_extents.width);
536
      draw_rectangle (stylesheet, cr, CC_FILLER, leftpos, y,
537
		      x-leftpos, h, TRUE, FALSE, NULL);
538
      draw_rectangle (stylesheet, cr, CC_FILLER, x+text_extents.width, y,
539
		      rightpos-(x+text_extents.width), h, TRUE, FALSE, NULL);
540
      break;
541
    case PANGO_ALIGN_RIGHT:
542
      x = rightpos - text_extents.width;
543
      draw_rectangle (stylesheet, cr, CC_FILLER, leftpos, y,
544
		      rightpos-(leftpos-text_extents.width), h, TRUE, FALSE, NULL);
545
      break;
546
    default:
547
      g_error ("Unknown alignment");
548
    }
549
550
  draw_rectangle (stylesheet, cr, CC_TITLE, x, y, text_extents.width, h, TRUE, FALSE, layout);
551
552
  g_object_unref (G_OBJECT (layout));
553
  cairo_destroy (cr);
554
}
555
556
int
557
main (int	  argc,
558
      char	**argv)
559
{
560
	ccss_grammar_t		*grammar;
561
	ccss_stylesheet_t	*stylesheet;
562
	ccss_style_t		*style;
563
564
	cairo_surface_t         *surface;
565
566
	int image_width = 800;
567
	int image_height = 250;
568
569
	initialise_classes ();
570
571
	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
572
					      image_width,
573
					      image_height);
574
575
	grammar = ccss_cairo_grammar_create ();
576
	ccss_grammar_add_functions (grammar, _functions);
577
	stylesheet = ccss_grammar_create_stylesheet_from_file (grammar,
578
							       argv[1],
579
							       NULL);
580
581
	render_copper (surface, stylesheet);
582
583
	ccss_stylesheet_destroy (stylesheet);
584
	ccss_grammar_destroy (grammar);
585
586
	cairo_surface_write_to_png (surface, argv[2]);
587
	cairo_surface_destroy (surface);
588
589
	return EXIT_SUCCESS;
590
}