OSDN Git Service

c7a4d535b8e3d68cc90dd610746bef51904017af
[putex/putex.git] / src / dvipdfmx-pu / src / spc_html.c
1 /*  
2
3     This is dvipdfmx, an eXtended version of dvipdfm by Mark A. Wicks.
4
5     Copyright (C) 2007-2012 by Jin-Hwan Cho and Shunsaku Hirata,
6     the dvipdfmx project team.
7     
8     Copyright (C) 1998, 1999 by Mark A. Wicks <mwicks@kettering.edu>
9
10     This program is free software; you can redistribute it and/or modify
11     it under the terms of the GNU General Public License as published by
12     the Free Software Foundation; either version 2 of the License, or
13     (at your option) any later version.
14     
15     This program is distributed in the hope that it will be useful,
16     but WITHOUT ANY WARRANTY; without even the implied warranty of
17     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18     GNU General Public License for more details.
19     
20     You should have received a copy of the GNU General Public License
21     along with this program; if not, write to the Free Software
22     Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
23 */
24
25 #ifdef HAVE_CONFIG_H
26 #include "config.h"
27 #endif
28
29 #include "system.h"
30 #include "mem.h"
31 #include "error.h"
32 #include "dpxutil.h"
33
34 #include "pdfdraw.h"
35 #include "pdfdev.h"
36 #include "pdfximage.h"
37
38 #include "pdfdoc.h"
39
40 #include "specials.h"
41 #include "spc_util.h"
42
43 #include "spc_html.h"
44
45
46 #define  ENABLE_HTML_IMG_SUPPORT   1
47 #define  ENABLE_HTML_SVG_TRANSFORM 1
48 #define  ENABLE_HTML_SVG_OPACITY   1
49
50 /* _FIXME_
51  * Please rewrite this or remove html special support
52  */
53
54 #define  ANCHOR_TYPE_HREF  0
55 #define  ANCHOR_TYPE_NAME  1
56
57 struct spc_html_
58 {
59   struct {
60     long  extensions;
61   } opts;
62
63   pdf_obj  *link_dict;
64   char     *baseurl;
65   int       pending_type;
66 };
67
68 static struct spc_html_ _html_state = {
69   { 0 },
70   NULL, NULL, -1
71 };
72
73
74 #ifdef  ENABLE_HTML_SVG_TRANSFORM
75 static int cvt_a_to_tmatrix (pdf_tmatrix *M, const char *ptr, const char **nextptr);
76 #endif /* ENABLE_HTML_SVG_TRANSFORM */
77
78 #define \
79 downcasify(s) \
80 if ((s)) { \
81   char  *_p = (char *) (s); \
82   while (*(_p) != 0) { \
83     if (*(_p) >= 'A' && *(_p) <= 'Z') { \
84       *(_p) = (*(_p) - 'A') + 'a'; \
85     } \
86     _p++; \
87   } \
88 }
89
90 static int
91 parse_key_val (const char **pp, const char *endptr, char **kp, char **vp)
92 {
93   const char *q, *p;
94   char  *k, *v;
95   int    n, error = 0;
96
97   for (p = *pp ; p < endptr && isspace(*p); p++);
98 #if  0
99   while (!error && p < endptr &&
100          ((*p >= 'a' && *p <= 'z') ||
101           (*p >= 'A' && *p <= 'Z'))
102         ) {
103 #endif
104     k = v = NULL;
105     for (q = p, n = 0;
106          p < endptr &&
107          ((*p >= 'a' && *p <= 'z') ||
108           (*p >= 'A' && *p <= 'Z') ||
109           (*p >= '0' && *p <= '9') ||
110            *p == '-' || *p == ':'
111          ); n++, p++);
112     if (n == 0) {
113 #if  0
114       break;
115 #else
116       *kp = *vp = NULL;
117       return  -1;
118 #endif
119     }
120     k = NEW(n + 1, char);
121     memcpy(k, q, n); k[n] = '\0';
122     if (p + 2 >= endptr || p[0] != '=' || (p[1] != '\"' && p[1] != '\'')) {
123       RELEASE(k); k = NULL;
124       *pp = p;
125       error = -1;
126     } else {
127       char  qchr = p[1];
128       p += 2; /* skip '="' */
129       for (q = p, n = 0; p < endptr && *p != qchr; p++, n++);
130       if (p == endptr || *p != qchr)
131         error = -1;
132       else {
133         v = NEW(n + 1, char);
134         memcpy(v, q, n); v[n] = '\0';
135 #if  0
136         pdf_add_dict(t->attr,
137                      pdf_new_name(k),
138                      pdf_new_string(v, n));
139         RELEASE(v);
140 #endif
141         p++;
142       }
143     }
144 #if  0
145     RELEASE(k);
146     if (!error)
147       for ( ; p < endptr && isspace(*p); p++);
148   }
149 #endif
150
151   *kp = k; *vp = v; *pp = p;
152   return  error;
153 }
154
155 #define  HTML_TAG_NAME_MAX    127
156 #define  HTML_TAG_TYPE_EMPTY  1
157 #define  HTML_TAG_TYPE_OPEN   1
158 #define  HTML_TAG_TYPE_CLOSE  2
159
160 static int
161 read_html_tag (char *name, pdf_obj *attr, int *type, const char **pp, const char *endptr)
162 {
163   const char *p = *pp;
164   int    n = 0, error = 0;
165
166   for ( ; p < endptr && isspace(*p); p++);
167   if (p >= endptr || *p != '<')
168     return  -1;
169
170   *type = HTML_TAG_TYPE_OPEN;
171   for (++p; p < endptr && isspace(*p); p++);
172   if (p < endptr && *p == '/') {
173     *type = HTML_TAG_TYPE_CLOSE;
174     for (++p; p < endptr && isspace(*p); p++);
175   }
176
177 #define ISDELIM(c) ((c) == '>' || (c) == '/' || isspace(c))
178   for (n = 0; p < endptr && n < HTML_TAG_NAME_MAX && !ISDELIM(*p); n++, p++) {
179     name[n] = *p;
180   } 
181   name[n] = '\0';
182   if (n == 0 || p == endptr || !ISDELIM(*p)) {
183     *pp = p;
184     return  -1;
185   }
186
187   for ( ; p < endptr && isspace(*p); p++);
188   while (p < endptr && !error && *p != '/' && *p != '>') {
189     char  *kp = NULL, *vp = NULL;
190     error = parse_key_val(&p, endptr, &kp, &vp);
191     if (!error) {
192       downcasify(kp);
193       pdf_add_dict(attr,
194                    pdf_new_name(kp),
195                    pdf_new_string(vp, strlen(vp) + 1)); /* include trailing NULL here!!! */
196       RELEASE(kp);
197       RELEASE(vp);
198     }
199     for ( ; p < endptr && isspace(*p); p++);
200   }
201   if (error) {
202     *pp = p;
203     return  error;
204   }
205
206   if (p < endptr && *p == '/') {
207     *type = HTML_TAG_TYPE_EMPTY;
208     for (++p; p < endptr && isspace(*p); p++);
209   }
210   if (p == endptr || *p != '>') {
211     *pp = p;
212     return  -1;
213   }
214   p++;
215
216   downcasify(name);
217   *pp = p;
218   return  0;
219 }
220
221
222 static int
223 spc_handler_html__init (struct spc_env *spe, struct spc_arg *ap, void *dp)
224 {
225   struct spc_html_ *sd = dp;
226
227   sd->link_dict    = NULL;
228   sd->baseurl      = NULL;
229   sd->pending_type = -1;
230
231   return  0;
232 }
233
234 static int
235 spc_handler_html__clean (struct spc_env *spe, struct spc_arg *ap, void *dp)
236 {
237   struct spc_html_ *sd = dp;
238
239   if (sd->baseurl)
240     RELEASE(sd->baseurl);
241
242   if (sd->pending_type >= 0 || sd->link_dict)
243     spc_warn(spe, "Unclosed html anchor found.");
244
245   if (sd->link_dict)
246     pdf_release_obj(sd->link_dict);
247
248   sd->pending_type = -1;
249   sd->baseurl      = NULL;
250   sd->link_dict    = NULL;
251
252   return  0;
253 }
254
255
256 static int
257 spc_handler_html__bophook (struct spc_env *spe, struct spc_arg *ap, void *dp)
258 {
259   struct spc_html_ *sd = dp;
260
261   if (sd->pending_type >= 0) {
262     spc_warn(spe, "...html anchor continues from previous page processed...");
263   }
264
265   return  0;
266 }
267
268 static int
269 spc_handler_html__eophook (struct spc_env *spe, struct spc_arg *ap, void *dp)
270 {
271   struct spc_html_ *sd = dp;
272
273   if (sd->pending_type >= 0) {
274     spc_warn(spe, "Unclosed html anchor at end-of-page!");
275   }
276
277   return  0;
278 }
279
280
281 static char *
282 fqurl (const char *baseurl, const char *name)
283 {
284   char  *q;
285   int    len = 0;
286
287   len = strlen(name);
288   if (baseurl)
289     len += strlen(baseurl) + 1; /* we may want to add '/' */
290
291   q = NEW(len + 1, char);
292   *q = '\0';
293   if (baseurl && baseurl[0]) {
294     char  *p;
295     strcpy(q, baseurl);
296     p = q + strlen(q) - 1;
297     if (*p == '/')
298       *p = '\0';
299     if (name[0] && name[0] != '/')
300       strcat(q, "/");
301   }
302   strcat(q, name);
303
304   return  q;
305 }
306
307 static int
308 html_open_link (struct spc_env *spe, const char *name, struct spc_html_ *sd)
309 {
310   pdf_obj  *color;
311   char     *url;
312
313   ASSERT( name );
314   ASSERT( sd->link_dict == NULL ); /* Should be checked somewhere else */
315
316   sd->link_dict = pdf_new_dict();
317   pdf_add_dict(sd->link_dict,
318                pdf_new_name("Type"),    pdf_new_name ("Annot"));
319   pdf_add_dict(sd->link_dict,
320                pdf_new_name("Subtype"), pdf_new_name ("Link"));
321
322   color = pdf_new_array ();
323   pdf_add_array(color, pdf_new_number(0.0));
324   pdf_add_array(color, pdf_new_number(0.0));
325   pdf_add_array(color, pdf_new_number(1.0));
326   pdf_add_dict(sd->link_dict, pdf_new_name("C"), color);
327
328   url = fqurl(sd->baseurl, name);
329   if (url[0] == '#') {
330     /* url++; causes memory leak in RELEASE(url) */
331     pdf_add_dict(sd->link_dict,
332                  pdf_new_name("Dest"),
333                  pdf_new_string(url+1, strlen(url+1)));
334   } else { /* Assume this is URL */
335     pdf_obj  *action = pdf_new_dict();
336     pdf_add_dict(action,
337                  pdf_new_name("Type"),
338                  pdf_new_name("Action"));
339     pdf_add_dict(action,
340                  pdf_new_name("S"),
341                  pdf_new_name("URI"));
342     pdf_add_dict(action,
343                  pdf_new_name("URI"),
344                  pdf_new_string(url, strlen(url)));
345     pdf_add_dict(sd->link_dict,
346                  pdf_new_name("A"),
347                  pdf_link_obj(action));
348     pdf_release_obj(action);
349   }
350   RELEASE(url);
351
352   spc_begin_annot(spe, sd->link_dict);
353
354   sd->pending_type = ANCHOR_TYPE_HREF;
355
356   return  0;
357 }
358
359 static int
360 html_open_dest (struct spc_env *spe, const char *name, struct spc_html_ *sd)
361 {
362   int        error;
363   pdf_obj   *array, *page_ref;
364   pdf_coord  cp;
365
366   cp.x = spe->x_user; cp.y = spe->y_user;
367   pdf_dev_transform(&cp, NULL);
368
369   page_ref = pdf_doc_this_page_ref();
370   ASSERT( page_ref ); /* Otherwise must be bug */
371
372   array = pdf_new_array();
373   pdf_add_array(array, page_ref);
374   pdf_add_array(array, pdf_new_name("XYZ"));
375   pdf_add_array(array, pdf_new_null());
376   pdf_add_array(array, pdf_new_number(cp.y + 24.0));
377   pdf_add_array(array, pdf_new_null());
378
379   error = pdf_doc_add_names("Dests",
380                             name, strlen(name),
381                             array);
382
383   if (error)
384     spc_warn(spe, "Failed to add named destination: %s", name);
385
386   sd->pending_type = ANCHOR_TYPE_NAME;
387
388   return  error;
389 }
390
391 #define ANCHOR_STARTED(s) ((s)->pending_type >= 0 || (s)->link_dict)
392
393 static int
394 spc_html__anchor_open (struct spc_env *spe, pdf_obj *attr, struct spc_html_ *sd)
395 {
396   pdf_obj *href, *name;
397   int      error = 0;
398
399   if (ANCHOR_STARTED(sd)) {
400     spc_warn(spe, "Nested html anchors found!");
401     return  -1;
402   }
403
404   href = pdf_lookup_dict(attr, "href");
405   name = pdf_lookup_dict(attr, "name");
406   if (href && name) {
407     spc_warn(spe, "Sorry, you can't have both \"href\" and \"name\" in anchor tag...");
408     error = -1;
409   } else if (href) {
410     error = html_open_link(spe, pdf_string_value(href), sd);
411   } else if (name) { /* name */
412     error = html_open_dest(spe, pdf_string_value(name), sd);
413   } else {
414     spc_warn(spe, "You should have \"href\" or \"name\" in anchor tag!");
415     error = -1;
416   }
417
418   return  error;
419 }
420
421 static int
422 spc_html__anchor_close (struct spc_env *spe, pdf_obj *attr, struct spc_html_ *sd)
423 {
424   int  error = 0;
425
426   switch (sd->pending_type) {
427   case  ANCHOR_TYPE_HREF:
428     if (sd->link_dict) {
429       spc_end_annot(spe);
430       pdf_release_obj(sd->link_dict);
431       sd->link_dict    = NULL;
432       sd->pending_type = -1;
433     } else {
434       spc_warn(spe, "Closing html anchor (link) without starting!");
435       error = -1;
436     }
437     break;
438   case  ANCHOR_TYPE_NAME:
439     sd->pending_type = -1;
440     break;
441   default:
442     spc_warn(spe, "No corresponding opening tag for html anchor.");
443     error = -1;
444     break;
445   }
446
447   return  error;
448 }
449
450 static int
451 spc_html__base_empty (struct spc_env *spe, pdf_obj *attr, struct spc_html_ *sd)
452 {
453   pdf_obj *href;
454   char    *vp;
455
456   href = pdf_lookup_dict(attr, "href");
457   if (!href) {
458     spc_warn(spe, "\"href\" not found for \"base\" tag!");
459     return  -1;
460   }
461
462   vp = (char *) pdf_string_value(href);
463   if (sd->baseurl) {
464     spc_warn(spe, "\"baseurl\" changed: \"%s\" --> \"%s\"", sd->baseurl, vp);
465     RELEASE(sd->baseurl);
466   }
467   sd->baseurl = NEW(strlen(vp) + 1, char);
468   strcpy(sd->baseurl, vp);
469
470   return  0;
471 }
472
473
474 #ifdef  ENABLE_HTML_IMG_SUPPORT
475 /* This isn't completed.
476  * Please think about placement of images.
477  */
478 static double
479 atopt (const char *a)
480 {
481   char   *q;
482   const char *p = a;
483   double  v, u = 1.0;
484   const char *_ukeys[] = {
485 #define K_UNIT__PT  0
486 #define K_UNIT__IN  1
487 #define K_UNIT__CM  2
488 #define K_UNIT__MM  3
489 #define K_UNIT__BP  4
490     "pt", "in", "cm", "mm", "bp",
491 #define K_UNIT__PX  5
492     "px",
493      NULL
494   };
495   int     k;
496
497   q = parse_float_decimal(&p, p + strlen(p));
498   if (!q) {
499     WARN("Invalid length value: %s (%c)", a, *p);
500     return  0.0;
501   }
502
503   v = atof(q);
504   RELEASE(q);
505
506   q = parse_c_ident(&p, p + strlen(p));
507   if (q) {
508     for (k = 0; _ukeys[k] && strcmp(_ukeys[k], q); k++);
509     switch (k) {
510     case K_UNIT__PT: u *= 72.0 / 72.27; break;
511     case K_UNIT__IN: u *= 72.0; break;
512     case K_UNIT__CM: u *= 72.0 / 2.54 ; break;
513     case K_UNIT__MM: u *= 72.0 / 25.4 ; break;
514     case K_UNIT__BP: u *= 1.0 ; break;
515     case K_UNIT__PX: u *= 1.0 ; break; /* 72dpi */
516     default:
517       WARN("Unknown unit of measure: %s", q);
518       break;
519     }
520     RELEASE(q);
521   }
522
523   return  v * u;
524 }
525
526
527 #ifdef  ENABLE_HTML_SVG_OPACITY
528 /* Replicated from spc_tpic */
529 static pdf_obj *
530 create_xgstate (double a /* alpha */, int f_ais /* alpha is shape */)
531 {
532   pdf_obj  *dict;
533
534   dict = pdf_new_dict();
535   pdf_add_dict(dict,
536                pdf_new_name("Type"),
537                pdf_new_name("ExtGState"));
538   if (f_ais) {
539     pdf_add_dict(dict,
540                  pdf_new_name("AIS"),
541                  pdf_new_boolean(1));
542   }
543   pdf_add_dict(dict,
544                pdf_new_name("ca"),
545                pdf_new_number(a));
546
547   return  dict;
548 }
549
550 static int
551 check_resourcestatus (const char *category, const char *resname)
552 {
553   pdf_obj  *dict1, *dict2;
554
555   dict1 = pdf_doc_current_page_resources();
556   if (!dict1)
557     return  0;
558
559   dict2 = pdf_lookup_dict(dict1, category);
560   if (dict2 &&
561       pdf_obj_typeof(dict2) == PDF_DICT) {
562     if (pdf_lookup_dict(dict2, resname))
563       return  1;
564   }
565   return  0;
566 }
567 #endif /* ENABLE_HTML_SVG_OPACITY */
568
569 static int
570 spc_html__img_empty (struct spc_env *spe, pdf_obj *attr, struct spc_html_ *sd)
571 {
572   pdf_obj       *src, *obj;
573   transform_info ti;
574   int            id, error = 0;
575 #ifdef  ENABLE_HTML_SVG_OPACITY
576   double         alpha = 1.0; /* meaning fully opaque */
577 #endif /* ENABLE_HTML_SVG_OPACITY */
578 #ifdef  ENABLE_HTML_SVG_TRANSFORM
579   pdf_tmatrix    M, M1;
580
581   pdf_setmatrix(&M, 1.0, 0.0, 0.0, 1.0, spe->x_user, spe->y_user);
582 #endif /* ENABLE_HTML_SVG_TRANSFORM */
583
584   spc_warn(spe, "html \"img\" tag found (not completed, plese don't use!).");
585
586   src = pdf_lookup_dict(attr, "src");
587   if (!src) {
588     spc_warn(spe, "\"src\" attribute not found for \"img\" tag!");
589     return  -1;
590   }
591
592   transform_info_clear(&ti);
593   obj = pdf_lookup_dict(attr, "width");
594   if (obj) {
595     ti.width  = atopt(pdf_string_value(obj));
596     ti.flags |= INFO_HAS_WIDTH;
597   }
598   obj = pdf_lookup_dict(attr, "height");
599   if (obj) {
600     ti.height = atopt(pdf_string_value(obj));
601     ti.flags |= INFO_HAS_HEIGHT;
602   }
603
604 #ifdef  ENABLE_HTML_SVG_OPACITY
605   obj = pdf_lookup_dict(attr, "svg:opacity");
606   if (obj) {
607     alpha = atof(pdf_string_value(obj));
608     if (alpha < 0.0 || alpha > 1.0) {
609       spc_warn(spe, "Invalid opacity value: %s", pdf_string_value(obj));
610       alpha = 1.0;
611     }
612   }
613 #endif /* ENABLE_HTML_SVG_OPCAITY */
614
615 #ifdef  ENABLE_HTML_SVG_TRANSFORM
616   obj = pdf_lookup_dict(attr, "svg:transform");
617   if (obj) {
618     const char *p = pdf_string_value(obj);
619     pdf_tmatrix  N;
620     for ( ; *p && isspace(*p); p++);
621     while (*p && !error) {
622       pdf_setmatrix(&N, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0);
623       error = cvt_a_to_tmatrix(&N, p, &p);
624       if (!error) {
625         N.f = -N.f;
626         pdf_concatmatrix(&M, &N);
627         for ( ; *p && isspace(*p); p++);
628         if (*p == ',')
629           for (++p; *p && isspace(*p); p++);
630       }
631     }
632   }
633 #endif /* ENABLE_HTML_SVG_TRANSFORM */
634
635   if (error) {
636     spc_warn(spe, "Error in html \"img\" tag attribute.");
637     return  error;
638   }
639
640   id = pdf_ximage_findresource(pdf_string_value(src), 0, NULL);
641   if (id < 0) {
642     spc_warn(spe, "Could not find/load image: %s", pdf_string_value(src)); 
643     error = -1;
644   } else {
645 #if defined(ENABLE_HTML_SVG_TRANSFORM) || defined(ENABLE_HTML_SVG_OPACITY)
646     {
647       char     *res_name;
648       pdf_rect  r;
649
650       graphics_mode();
651
652       pdf_dev_gsave();
653
654 #ifdef  ENABLE_HTML_SVG_OPACITY
655       {
656         pdf_obj *dict;
657         int      a = round(100.0 * alpha);
658         if (a != 0) {
659           res_name = NEW(strlen("_Tps_a100_") + 1, char);
660           sprintf(res_name, "_Tps_a%03d_", a); /* Not Tps prefix but... */
661           if (!check_resourcestatus("ExtGState", res_name)) {
662             dict = create_xgstate(round_at(0.01 * a, 0.01), 0);
663             pdf_doc_add_page_resource("ExtGState",
664                                       res_name, pdf_ref_obj(dict));
665             pdf_release_obj(dict);
666           }
667           pdf_doc_add_page_content(" /", 2);  /* op: */
668           pdf_doc_add_page_content(res_name, strlen(res_name));  /* op: */
669           pdf_doc_add_page_content(" gs", 3);  /* op: gs */
670           RELEASE(res_name);
671         }
672       }
673 #endif /* ENABLE_HTML_SVG_OPACITY */
674
675       pdf_ximage_scale_image(id, &M1, &r, &ti);
676       pdf_concatmatrix(&M, &M1);
677       pdf_dev_concat(&M);
678
679       pdf_dev_rectclip(r.llx, r.lly, r.urx - r.llx, r.ury - r.lly);
680
681       res_name = pdf_ximage_get_resname(id);
682       pdf_doc_add_page_content(" /", 2);  /* op: */
683       pdf_doc_add_page_content(res_name, strlen(res_name));  /* op: */
684       pdf_doc_add_page_content(" Do", 3);  /* op: Do */
685
686       pdf_dev_grestore();
687
688       pdf_doc_add_page_resource("XObject",
689                                 res_name,
690                                 pdf_ximage_get_reference(id));
691     }
692 #else
693     pdf_dev_put_image(id, &ti, spe->x_user, spe->y_user);
694 #endif /* ENABLE_HTML_SVG_XXX */
695   }
696
697   return  error;
698 }
699 #else
700 static int
701 spc_html__img_empty (struct spc_env *spe, pdf_obj *attr, struct spc_html_ *sd)
702 {
703   spc_warn(spe, "IMG tag not yet supported yet...");
704   return  -1;
705 }
706 #endif  /* ENABLE_HTML_IMG_SUPPORT */
707
708
709 static int
710 spc_handler_html_default (struct spc_env *spe, struct spc_arg *ap)
711 {
712   struct spc_html_ *sd = &_html_state;
713   char      name[HTML_TAG_NAME_MAX + 1];
714   pdf_obj  *attr;
715   int       error = 0, type = HTML_TAG_TYPE_OPEN;
716
717   if (ap->curptr >= ap->endptr)
718     return  0;
719
720   attr  = pdf_new_dict();
721   error = read_html_tag(name, attr, &type, &ap->curptr, ap->endptr);
722   if (error) {
723     pdf_release_obj(attr);
724     return  error;
725   }
726   if (!strcmp(name, "a")) {
727     switch (type) {
728     case  HTML_TAG_TYPE_OPEN:
729       error = spc_html__anchor_open (spe, attr, sd);
730       break;
731     case  HTML_TAG_TYPE_CLOSE:
732       error = spc_html__anchor_close(spe, attr, sd);
733       break;
734     default:
735       spc_warn(spe, "Empty html anchor tag???");
736       error = -1;
737       break;
738     }
739   } else if (!strcmp(name, "base")) {
740     if (type == HTML_TAG_TYPE_CLOSE) {
741       spc_warn(spe, "Close tag for \"base\"???");
742       error = -1;
743     } else { /* treat "open" same as "empty" */
744       error = spc_html__base_empty(spe, attr, sd);
745     }
746   } else if (!strcmp(name, "img")) {
747     if (type == HTML_TAG_TYPE_CLOSE) {
748       spc_warn(spe, "Close tag for \"img\"???");
749       error = -1;
750     } else { /* treat "open" same as "empty" */
751       error = spc_html__img_empty(spe, attr, sd);
752     }
753   }
754   pdf_release_obj(attr);
755
756   for ( ; ap->curptr < ap->endptr && isspace(ap->curptr[0]); ap->curptr++);
757
758   return  error;
759 }
760
761
762 #ifdef  ENABLE_HTML_SVG_TRANSFORM
763 /* translate wsp* '(' wsp* number (comma-wsp number)? wsp* ')' */
764 static int
765 cvt_a_to_tmatrix (pdf_tmatrix *M, const char *ptr, const char **nextptr)
766 {
767   char        *q;
768   const char  *p = ptr;
769   int          n;
770   double       v[6];
771   static const char *_tkeys[] = {
772 #define  K_TRNS__MATRIX     0
773     "matrix",    /* a b c d e f */
774 #define  K_TRNS__TRANSLATE  1
775     "translate", /* tx [ty] : dflt. tf = 0 */
776 #define  K_TRNS__SCALE      2
777     "scale",     /* sx [sy] : dflt. sy = sx */
778 #define  K_TRNS__ROTATE     3
779     "rotate",    /* ang [cx cy] : dflt. cx, cy = 0 */
780 #define  K_TRNS__SKEWX      4
781 #define  K_TRNS__SKEWY      5
782     "skewX",     /* ang */
783     "skewY",     /* ang */
784     NULL
785   };
786   int          k;
787
788   for ( ; *p && isspace(*p); p++);
789
790   q = parse_c_ident(&p, p + strlen(p));
791   if (!q)
792     return -1;
793   /* parsed transformation key */
794   for (k = 0; _tkeys[k] && strcmp(q, _tkeys[k]); k++);
795   RELEASE(q);
796
797   /* handle args */
798   for ( ; *p && isspace(*p); p++);
799   if (*p != '(' || *(p + 1) == 0)
800     return  -1;
801   for (++p; *p && isspace(*p); p++);
802   for (n = 0; n < 6 && *p && *p != ')'; n++) {
803     q = parse_float_decimal(&p, p + strlen(p));
804     if (!q)
805       break;
806     else {
807       v[n] = atof(q);
808       if (*p == ',')
809         p++;
810       for ( ; *p && isspace(*p); p++);
811       if (*p == ',')
812         for (++p; *p && isspace(*p); p++);
813       RELEASE(q);
814     }
815   }
816   if (*p != ')')
817     return  -1;
818   p++;
819
820   switch (k) {
821   case  K_TRNS__MATRIX:
822     if (n != 6)
823       return  -1;
824     M->a = v[0]; M->c = v[1];
825     M->b = v[2]; M->d = v[3];
826     M->e = v[4]; M->f = v[5];
827     break;
828   case  K_TRNS__TRANSLATE:
829     if (n != 1 && n != 2)
830       return  -1;
831     M->a = M->d = 1.0;
832     M->c = M->b = 0.0;
833     M->e = v[0]; M->f = (n == 2) ? v[1] : 0.0;
834     break;
835   case  K_TRNS__SCALE:
836     if (n != 1 && n != 2)
837       return  -1;
838     M->a = v[0]; M->d = (n == 2) ? v[1] : v[0];
839     M->c = M->b = 0.0;
840     M->e = M->f = 0.0;
841     break;
842   case  K_TRNS__ROTATE:
843     if (n != 1 && n != 3)
844       return  -1;
845     M->a = cos(v[0] * M_PI / 180.0);
846     M->c = sin(v[0] * M_PI / 180.0);
847     M->b = -M->c; M->d = M->a;
848     M->e = (n == 3) ? v[1] : 0.0;
849     M->f = (n == 3) ? v[2] : 0.0;
850     break;
851   case  K_TRNS__SKEWX:
852     if (n != 1)
853        return  -1;
854     M->a = M->d = 1.0;
855     M->c = 0.0;
856     M->b = tan(v[0] * M_PI / 180.0);
857     break;
858   case  K_TRNS__SKEWY:
859     if (n != 1)
860        return  -1;
861     M->a = M->d = 1.0;
862     M->c = tan(v[0] * M_PI / 180.0);
863     M->b = 0.0;
864     break;
865   }
866
867   if (nextptr)
868     *nextptr = p;
869   return  0;
870 }    
871 #endif /* ENABLE_HTML_SVG_TRANSFORM */
872
873 int
874 spc_html_at_begin_document (void)
875 {
876   struct spc_html_ *sd = &_html_state;
877   return  spc_handler_html__init(NULL, NULL, sd);
878 }
879
880 int
881 spc_html_at_begin_page (void)
882 {
883   struct spc_html_ *sd = &_html_state;
884   return  spc_handler_html__bophook(NULL, NULL, sd);
885 }
886
887 int
888 spc_html_at_end_page (void)
889 {
890   struct spc_html_ *sd = &_html_state;
891   return  spc_handler_html__eophook(NULL, NULL, sd);
892 }
893
894 int
895 spc_html_at_end_document (void)
896 {
897   struct spc_html_ *sd = &_html_state;
898   return  spc_handler_html__clean(NULL, NULL, sd);
899 }
900
901
902 int
903 spc_html_check_special (const char *buffer, long size)
904 {
905   const char *p, *endptr;
906
907   p      = buffer;
908   endptr = p + size;
909
910   for ( ; p < endptr && isspace(*p); p++);
911   size   = (long) (endptr - p);
912   if (size >= strlen("html:") &&
913       !memcmp(p, "html:", strlen("html:"))) {
914     return  1;
915   }
916
917   return  0;
918 }
919
920
921 int
922 spc_html_setup_handler (struct spc_handler *sph,
923                         struct spc_env *spe, struct spc_arg *ap)
924 {
925   ASSERT(sph && spe && ap);
926
927   for ( ; ap->curptr < ap->endptr && isspace(ap->curptr[0]); ap->curptr++);
928   if (ap->curptr + strlen("html:") > ap->endptr ||
929       memcmp(ap->curptr, "html:", strlen("html:"))) {
930     return  -1;
931   }
932
933   ap->command = "";
934
935   sph->key    = "html:";
936   sph->exec   = &spc_handler_html_default;
937
938   ap->curptr += strlen("html:");
939   for ( ; ap->curptr < ap->endptr && isspace(ap->curptr[0]); ap->curptr++);
940
941   return  0;
942 }
943