1 ;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: RENDERER; Readtable: GLISP; -*-
2 ;;; ---------------------------------------------------------------------------
3 ;;; Title: HTML-Specific Parts of the Renderer
4 ;;; Created: 1999-05-25
5 ;;; Author: Gilbert Baumann <gilbert@base-engineering.com>
6 ;;; License: MIT style (see below)
7 ;;; ---------------------------------------------------------------------------
8 ;;; (c) copyright 1998,1999 by Gilbert Baumann
10 ;;; Permission is hereby granted, free of charge, to any person obtaining
11 ;;; a copy of this software and associated documentation files (the
12 ;;; "Software"), to deal in the Software without restriction, including
13 ;;; without limitation the rights to use, copy, modify, merge, publish,
14 ;;; distribute, sublicense, and/or sell copies of the Software, and to
15 ;;; permit persons to whom the Software is furnished to do so, subject to
16 ;;; the following conditions:
18 ;;; The above copyright notice and this permission notice shall be
19 ;;; included in all copies or substantial portions of the Software.
21 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24 ;;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
25 ;;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
26 ;;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27 ;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
33 ;; This only recently occured to me:
35 ;; Most of the mappings here are rather primitive and a lot of are
36 ;; already handled in the default style sheet, so the idea would be to
37 ;; _compile_ the whole set of mappings, maybe even yielding a
38 ;; different kind of style object, so that we'd spend less time on
45 ;; ----------------------------------------------------------------------------
46 ;; 2001-05-14 GB - primitive caching of parsed style sheets
48 ;; 1999-08-21 GB - MAKE-TEXT-REPLACEMENT, MAKE-PASSWORD-REPLACEMENT,
49 ;; MAKE-OPTION-MENU-REPLACEMENT, MAKE-TEXT-AREA-REPLACEMENT,
50 ;; REPLACED-ELEMENT/INPUT: pass text-style
51 ;; - MAKE-TEXT-AREA-REPLACEMENT, REPLACED-ELEMENT/INPUT:
55 ;; 1999-08-19 GB - changed layout of :%REPLACEMENT attribute and
56 ;; PT-%REPLACEMENT function.
57 ;; - REPLACED-ELEMENT-P: returns two values now.
58 ;; - IMPLICIT-STYLE/IMG: IMG.border now maps to `border-width'
59 ;; - COMMAND-DOCUMENTATION: new generic function
60 ;; - COMMAND-CURSOR: new generic function
61 ;; - SERVER-SIDE-IMAGE-MAP: new class
62 ;; - FIND-A-ELEMENT-FOR-IMG-ISMAP: new function
63 ;; - IMAGE-REPLACED-ELEMENT/AUX: ISMAP support
64 ;; - GRAPHICAL-SUBMIT: new class
65 ;; - polygon image maps should work now.
67 ;; 1999-08-18 GB - PT-STYLE-STYLE now returns a list of assignments to
68 ;; carry "!important" to LOOKUP-STYLE.
70 ;; 1999-08-15 GB - PT-STYLE-STYLE new function to return style by STYLE
73 (in-package :RENDERER
)
75 (defun pt-%replacement
(pt &optional default
)
76 (sgml:pt-attr pt
:%replacement default
))
78 (defun (setf pt-%replacement
) (new-value pt
&optional default
)
80 (setf (sgml:pt-attr pt
:%replacement
) new-value
))
82 (defun pt-effective-url-attr (doc pt attr
)
83 "Return the parsed and merged effective URL of an elements attribute."
84 (url:merge-url
(url:parse-url
(pt-attr/latin1 pt attr
""))
85 (document-base-url doc
)))
87 (defun pt-body-element (pt)
89 ((eq (element-gi pt
) :HTML
)
90 (dolist (q (element-children pt
))
91 (when (eq (element-gi q
) :BODY
)
93 ((pt-body-element (element-parent pt
))) ))
95 (defun pt-all-data (x)
96 (cond ((member (element-gi x
) '(:pcdata
:comment
))
97 (map 'string
(lambda (x) (or (rune-char x
) #\?))
99 ((apply 'concatenate
'string
100 (mapcar #'pt-all-data
(element-children x
))))))
102 ;;; ---- Implicit Style -----------------------------------------------------------------------
104 (defun pt-attr/low
(pt prop
&optional
(default nil
))
105 (if (text-element-p pt
)
107 (element-attribute pt prop
)))
109 (defun pt-attr* (pt attr
&optional default
)
110 (pt-attr/low pt attr default
))
112 (defun pt-attr/latin1
(pt attr
&optional default
)
113 (let ((r (pt-attr/low pt attr
)))
115 (map 'string
(lambda (x) (if (< (rune-code x
) 256) (rune-char x
) #\?)) r
)
118 (defmethod closure-protocol:element-explicit-style
(document (pt sgml
::pt
))
119 (let ((x (pt-attr* pt
:style
)))
121 (let ((css::*style-sheet-base-url
* (document-base-url document
)))
122 (css::parse-assignment-list x
)))))
128 (defun pt-attr-warn (pt format
&rest args
)
129 (apply #'warn format args
))
131 (defun pt-attr/list-style-type
(pt slot
&optional
(default nil
))
132 (let ((v (pt-attr* pt slot nil
)))
134 (parse-list-style-type pt v
)
137 (defun parse-list-style-type (pt value
)
138 ;; Translate the HTML notation of a list-style-type 'value' to the CSS-1 notation.
139 (when (typep value
'rod
)
140 (setf value
(map 'string
(lambda (x) (or (code-char x
) #\?)) value
)))
141 (cond ((string= value
"1") :decimal
)
142 ((string= value
"a") :lower-alpha
)
143 ((string= value
"A") :upper-alpha
)
144 ((string= value
"i") :lower-roman
)
145 ((string= value
"I") :upper-roman
)
146 ((string-equal value
"disc") :disc
)
147 ((string-equal value
"square") :square
)
148 ((string-equal value
"circle") :circle
)
150 (pt-attr-warn pt
"The value '~S' is unknown to the 'TYPE' slot." value
)
151 (if (eq (element-gi (element-parent pt
)) :UL
) :disc
:decimal
)) ))
153 (defun parse-html-multi-length (string &optional
(error-p t
))
154 ;; -> list of (:% . N) | (* . N) | (:px . N) | NIL
155 (cond ((parse-html-length string nil
))
156 ((and (> (length string
) 1)
157 (char= (char string
0) #\
*)
158 (every #'digit-char-p
(subseq string
1)))
159 (cons '* (parse-integer (subseq string
1))))
160 ((and (> (length string
) 1)
161 (char= (char string
(1- (length string
))) #\
*)
162 (every #'digit-char-p
(subseq string
0 (1- (length string
)))))
163 (cons '* (parse-integer (subseq string
0 (1- (length string
))))))
164 ((string-equal string
"*") ; '*' is short cut for '1*'
167 (error "'~S' is not a valid HTML 'MultiLength'." string
))
171 (defun parse-html-length (string &optional
(error-p t
))
172 ;; -> (:% . N) | (:px . N) | NIL
173 (cond ((and (> (length string
) 0) (every #'digit-char-p string
))
174 (cons :px
(parse-integer string
)))
175 ((and (> (length string
) 1)
176 (char= (char string
(1- (length string
))) #\%
)
177 (every #'digit-char-p
(subseq string
0 (1- (length string
)))))
178 (cons :%
(parse-integer (subseq string
0 (1- (length string
))))))
180 (error "'~S' is not a valid HTML 'Length'." string
))
184 (defun unparse-html-length (value)
185 (cond ((and (consp value
) (eq (car value
) :%
))
186 (format nil
"~D%" (cdr value
)))
187 ((and (consp value
) (eq (car value
) :px
))
188 (format nil
"~D" (cdr value
)))
192 (format nil
"[invalid html-length: ~S]" value
))))
194 (defun pt-attr/with-parser
(pt slot default parser pretty-type-name
)
195 (let ((s (pt-attr* pt slot
)))
198 (let ((value (funcall parser s
)))
202 (pt-attr-warn pt
"The value of the ~A attribute, ~S, is not ~A."
203 slot
(rod-string s
) pretty-type-name
)
206 (defun pt-attr/integer
(pt slot
&optional default
)
207 (pt-attr/with-parser pt slot default
#'html
/parse-integer
"an integer"))
209 (defun pt-attr/pixels
(pt slot
&optional default
)
210 (pt-attr/with-parser pt slot default
#'html
/parse-integer
"an integer"))
212 (defun pt-attr/length
(pt slot
&optional default
)
213 (pt-attr/with-parser pt slot default
#'html
/parse-length
"a length"))
215 (defun pt-attr/length-list
(pt slot
&optional default
)
216 (pt-attr/with-parser pt slot default
#'html
/parse-length-list
217 "a comma separated list of lengths"))
219 (defun pt-attr/multi-length
(pt slot
&optional default
)
220 (pt-attr/with-parser pt slot default
#'html
/parse-multi-length
"a length"))
222 (defun pt-attr/enum
(pt slot default keys
)
223 (let ((s (pt-attr* pt slot
)))
227 (let ((val (some (lambda (key)
228 (and (= (length s
) (length (symbol-name key
)))
230 (char-equal (rune-char x
) y
))
236 (pt-attr-warn pt
"The value of the ~A attribute, ~
237 should be ~{\"~A\"~#[~; or ~:;, ~]~}, but not ~S."
238 slot keys
(rod-string s
))
241 (defun pt-attr/table.frame
(pt slot
&optional default
)
242 (pt-attr/enum pt slot default
243 '(:void
:above
:below
:hsides
:lhs
:rhs
:vsides
:box
:border
)))
245 (defun pt-attr/table.rules
(pt slot
&optional default
)
246 (pt-attr/enum pt slot default
247 '(:none
:groups
:rows
:cols
:all
)))
249 (defun pt-attr/table.align
(pt slot
&optional default
)
250 (pt-attr/enum pt slot default
251 '(:left
:center
:right
)))
253 (defun pt-attr/cell-halign
(pt slot
&optional default
)
254 (pt-attr/enum pt slot default
255 '(:left
:center
:right
:justify
:char
)))
257 (defun pt-attr/cell-valign
(pt slot
&optional default
)
258 ;; Note for compatibility, we also accept :center but map that to :middle
259 ;; ### oops, :center actually is HTML, while :middle is CSSSpeak.
260 (subst :middle
:center
261 (pt-attr/enum pt slot default
262 '(:top
:middle
:bottom
:baseline
:center
))))
264 (defun pt-attr/input-type
(pt slot
&optional
(default :text
))
265 (pt-attr/enum pt slot default
266 '(:text
:password
:checkbox
:radio
:submit
:reset
267 :file
:hidden
:image
:button
)))
269 (defun pt-attr/img-align
(pt slot
&optional default
)
270 (pt-attr/enum pt slot default
'(:top
:middle
:bottom
:left
:right
)))
272 (defun pt-attr/align
(pt &optional
(slot :align
) (default nil
))
273 (pt-attr/enum pt slot default
'(:left
:center
:right
:justify
)))
275 (defun pt-attr/clear
(pt &optional
(slot :clear
) (default nil
))
276 (pt-attr/enum pt slot default
'(:left
:right
:all
:none
)))
278 (defun pt-attr/color
(pt slot
&optional
(default nil
))
279 (pt-attr/latin1 pt slot default
))
281 (defun pt-attr/boolean-flag
(pt slot
)
282 (eq (pt-attr/enum pt slot nil
(list slot
)) slot
))
286 ;; NOTE: The if-match macro is defined in match.lisp
288 (define-match-macro integer
(&optional
(radix 10))
290 (+ (p (lambda (ch) (digit-rune-p ch
,radix
))))))
292 (define-match-macro w
* ()
293 `(* (p #'white-space-rune-p
)))
295 (defun html/parse-integer
(s)
296 (if-match (s :type rod
:test
#'rune
=)
297 (& (w*) (= $res
(integer)) (w*))
298 (parse-integer (rod-string (subseq s $res-start $res-end
)))))
300 (defun html/parse-length
(s)
302 (if-match (s :type rod
:test
#'rune
=)
303 (& (w*) (= $res
(integer)) (w*))
304 (cons :px
(parse-integer (rod-string (subseq s $res-start $res-end
)))))
305 (if-match (s :type rod
:test
#'rune
=)
306 (& (w*) (= $res
(integer)) #/%
(w*))
307 (cons :%
(parse-integer (rod-string (subseq s $res-start $res-end
)))))))
309 (defun html/parse-multi-length
(s)
311 (html/parse-length s
)
312 (if-match (s :type rod
:test
#'rune
=)
313 (& (w*) (= $res
(integer)) #/* (w*))
314 (cons '* (parse-integer (rod-string (subseq s $res-start $res-end
)))))
315 ;; This below is illegal syntax '*i' is not allowed
317 (if-match (s :type rod
:test
#'rune
=)
318 (& (w*) #.
(char-code #\
*) (= $res
(integer)) (w*))
319 (cons '* (parse-integer (rod-string (subseq s $res-start $res-end
)))))
320 ;; "*" is abbrev for "1*"
321 (if-match (s :type rod
:test
#'rune
=)
325 (defun html/parse-length-list
(s)
326 ;; A comma separated list of lengths
327 (let ((q (mapcar #'html
/parse-length
(split-by (char-code #\
,) s
))))
328 (unless (member nil q
)
333 (defun pt-attr/link-types
(pt att
)
334 (pt-attr/with-parser pt att nil
338 (split-by-if #'white-space-rune-p
341 "a list of link types"))
343 (defun pt-attr/comma-separated-list
(pt att
)
344 (pt-attr/with-parser pt att nil
347 (mapcar #'(lambda (x)
348 (r2::rod-trim
(list 9 10 12 13 32) x
))
350 "a comma separated list"))
352 ;;;; --------------------------------------------------------------------------------
354 (defun find-html-head (pt)
355 (dolist (k (element-children (sgml:pt-root pt
)))
356 (cond ((member (element-gi k
) '(:HEAD
))
359 (defun pt-map-over-link-nodes (fn pt
)
360 "Map the function `fn' over all <LINK> nodes in the `pt' document's header."
361 (let ((head (find-html-head pt
)))
363 (dolist (k (element-children head
))
364 (when (eq (element-gi k
) :LINK
)
367 (defun the-style-node (pt)
368 "Given a parse tree, find the <STYLE> node in its header, if any."
369 (let ((head (find-html-head pt
)))
371 (dolist (k (element-children head
))
372 (when (eq (element-gi k
) :STYLE
)
377 (defparameter *style-sheet-cache
*
378 (make-hash-table :test
#'equalp
))
380 (defparameter *style-sheet-cache
*/lock
381 (bordeaux-threads:make-lock
"*style-sheet-cache*"))
383 (defun maybe-parse-style-sheet-from-url (url &key
(name "anonymous")
386 (multiple-value-bind (looked presentp
)
387 (bordeaux-threads:with-recursive-lock-held
(*style-sheet-cache
*/lock
)
388 (gethash url
*style-sheet-cache
*))
390 (format *debug-io
* "~&;; Serving style sheet ~S [at ~S] from cache.~%"
394 (format *debug-io
* "~&;; fetching and parsing style sheet ~S [at ~S].~%"
396 (let ((res (maybe-parse-style-sheet-from-url-aux
399 :supersheet supersheet
400 :media-type media-type
)))
401 (bordeaux-threads:with-recursive-lock-held
(*style-sheet-cache
*/lock
)
402 (setf (gethash url
*style-sheet-cache
*) res
))
405 (defun maybe-parse-style-sheet-from-url-aux (url &key
(name "anonymous")
408 (and (css::is-of-media-type-p
:screen media-type
) ;xxx
409 (let ((netlib::*always-use-cache-p
* t
)) ;hack to improve performance
410 (netlib:with-open-document
((input mime-type
) url
)
411 (cond ((and mime-type
(netlib::mime-type-equal mime-type
:text
:css
))
412 (multiple-value-bind (res condition
)
414 (css:parse-style-sheet input supersheet
417 :media-type media-type
))
419 (warn "Error while parsing style sheet from ~S:~% ~A"
425 (warn "The resource `~A' has mime type ~A, ~
426 but I expected text/css; style sheet ignored."
427 (url:unparse-url url
)
428 (and mime-type
(netlib::mime-type-name mime-type
)))
434 ;; * html-4.0.zip/struct/links.html#adef-rel:
436 ;; | This attribute describes the link from the current document
437 ;; | to the anchor specified by the href attribute. This value
438 ;; | of this attribute is one or more link types separated by
439 ;; | white space characters.
441 (defun style-link-does-apply-p (link select-style
)
442 (unless (equal (slot-value link
'media
) "print") ;### kludge
443 (cond ((set-equal (link-rel link
) '("stylesheet") :test
#'string-equal
)
445 (null (link-title link
)) ;persistent
446 (eq select-style
:default
))) ;selected
447 ((set-equal (link-rel link
) '("alternate" "stylesheet") :test
#'string-equal
)
449 (null (link-title link
)) ;persistent
450 (string-equal (link-title link
) select-style
))) )))
452 (defun style-sheet-link-p (link)
453 (or (set-equal (link-rel link
) (list "alternate" "stylesheet") :test
#'string-equal
)
454 (set-equal (link-rel link
) (list "stylesheet") :test
#'string-equal
)))
456 (defun alternate-style-sheet-link-p (link)
457 (or (set-equal (link-rel link
) (list "alternate" "stylesheet") :test
#'string-equal
)))
459 (defun default-style-sheet-link-p (link)
460 (or (set-equal (link-rel link
) (list "stylesheet") :test
#'string-equal
)))
462 (defun style-sheet-name-equal-p (x y
)
463 ;; just in case this changes
467 ;;;; --------------------------------------------------------------------------------
469 (defclass html-document-language
()
472 (defclass html-4.0-document-language
(html-document-language)
475 (defmethod closure-protocol:element-replaced-element-1
476 ((language html-4.0-document-language
)
477 user-agent document device
479 (declare (ignorable language user-agent
))
481 (cond ((and (not gui
:*user-wants-images-p
*)
482 (member (element-gi elm
) '(:IMG
)))
484 ((member (element-gi elm
) '(:IMG
))
485 ;;xxx (member (element-gi elm) '(:form :button :input :select :textarea :img))
486 (cond ((eq (pt-%replacement elm
:unset
) :unset
)
487 (multiple-value-bind (robj action-map
) (build-replaced-element document device elm
)
488 (setf (pt-%replacement elm
) (list robj action-map
) )))
490 (pt-%replacement elm
))))
494 ;;;; --------------------------------------------------------------------------------
496 (defun replaced-element-p (document device element
)
497 (closure-protocol:element-replaced-element-1
498 closure-protocol
:*document-language
*
499 closure-protocol
:*user-agent
*
500 document device element
))
502 (defun build-replaced-element (document device elm
)
503 (case (element-gi elm
)
505 (replaced-element/form document device elm
))
507 (handle-button device elm
))
509 (replaced-element/input document device elm
))
511 (make-option-menu-replacement device elm
))
513 (make-text-area-replacement document device elm
))
515 (image-replaced-element document device elm
))
519 (defun replaced-element/input
(document device elm
)
521 (case (pt-attr/input-type elm
:type
:text
)
523 (gui:ro
/make-submit-button
526 :label
(pt-attr* elm
:value
(rod "Submit Query"))
527 :name
(pt-attr* elm
:name
)
528 :size
(pt-attr/integer elm
:size nil
)
529 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
530 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
531 :text-style
(pt-text-style device elm
)
538 :label
(pt-attr* elm
:value
(rod "A Button"))
539 :name
(pt-attr* elm
:name
)
540 :size
(pt-attr/integer elm
:size nil
)
541 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
542 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
543 :text-style
(pt-text-style device elm
)
547 (gui:ro
/make-reset-button
550 :label
(pt-attr* elm
:value
(rod "Reset"))
551 :name
(pt-attr* elm
:name
)
552 :size
(pt-attr/integer elm
:size nil
)
553 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
554 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
555 :text-style
(pt-text-style device elm
)
559 (make-password-replacement device elm
))
562 (make-text-replacement device elm
))
565 (make-checkbox-replacement device elm
))
568 (make-radio-replacement device elm
))
571 ;; Hell, why is this ever reached?
572 ;; After all HIDDEN input element have display: none;
575 :name
(pt-attr* elm
:name
)
576 :value
(pt-attr* elm
:value
)
577 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)))
580 (let ((ro (image-replaced-element/aux document device elm
581 :uri
(pt-effective-url-attr document elm
:src
)
582 :alt
(pt-attr* elm
:alt
(rod "Submit Query")))))
583 ;; usemap from image-replaced-element/aux?!?
584 (let ((map (list (make-instance 'imap-everywhere
585 :link
(make-instance 'graphical-submit
587 :name
(pt-attr* elm
:name nil
))))))
591 (warn "Unrecognized input type: ~S." (pt-attr/input-type elm
:type
:text
))
594 (defun replaced-element/form
(document device elm
)
596 (setf (pt-%replacement elm
) nil
)
597 (sgml:map-pt
(lambda (x) (replaced-element-p document device x
)) elm
)
598 (pt-%replacement elm
:unset
))
600 (defun make-text-replacement (device elm
)
604 :name
(pt-attr* elm
:name
(rod ""))
605 :initial-value
(pt-attr* elm
:value
(rod ""))
606 :size
(pt-attr/integer elm
:size nil
)
607 :max-length
(pt-attr/integer elm
:maxlength nil
)
608 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
609 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
610 :text-style
(pt-text-style device elm
)))
612 (defun make-password-replacement (device elm
)
613 (gui:ro
/make-password
616 :name
(pt-attr* elm
:name
(rod ""));hmm warum "" hier?
617 :initial-value
(pt-attr* elm
:value
(rod ""))
618 :size
(pt-attr/integer elm
:size nil
)
619 :max-length
(pt-attr/integer elm
:maxlength nil
)
620 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
621 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
622 :text-style
(pt-text-style device elm
)))
624 (defun make-text-area-replacement (document device elm
)
625 (gui::ro
/make-text-area
628 :name
(pt-attr* elm
:name nil
)
629 :initial-value
(string-trim '(#\space
#\return
#\newline
#\tab
) (pt-data elm
))
630 :cols
(pt-attr/integer elm
:cols nil
)
631 :rows
(pt-attr/integer elm
:rows nil
)
632 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
633 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
634 :text-style
(pt-text-style device elm
)
637 (defun make-radio-replacement (device elm
)
638 (gui::ro
/make-radio-box
641 :name
(pt-attr* elm
:name
)
642 :initial-value
(pt-attr* elm
:value
)
643 :checked-p
(pt-attr/boolean-flag elm
:checked
)
644 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
645 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
646 :size
(pt-attr/integer elm
:size nil
)))
648 (defun make-checkbox-replacement (device elm
)
649 (gui:ro
/make-check-box
651 :name
(pt-attr* elm
:name
)
652 :initial-value
(pt-attr* elm
:value
)
653 :checked-p
(pt-attr/boolean-flag elm
:checked
)
654 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
655 :read-only-p
(pt-attr/boolean-flag elm
:readonly
)
656 :size
(pt-attr/integer elm
:size nil
)))
658 (defun rod-trim (bag rod
)
659 (let ((p1 (position-if-not (lambda (x) (member x bag
)) rod
))
660 (p2 (position-if-not (lambda (x) (member x bag
)) rod
:from-end t
)))
662 (subseq rod p1
(+ p2
1))
665 (defun parse-options (elm)
666 (case (element-gi elm
)
668 (let ((content (pt-data-iso10646 elm
) ))
669 (setf content
(and content
(rod-trim '(9 10 12 13 32) content
)))
670 (gui:make-option-menu-option
671 :label
(pt-attr* elm
:label content
)
672 :value
(or (pt-attr* elm
:value content
))
673 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
674 :selected-p
(pt-attr/boolean-flag elm
:selected
)
677 (gui:make-option-menu-option-group
678 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
679 :label
(pt-attr* elm
:label
(rod "--submenu--"))
680 :children
(remove nil
(mapcar #'parse-options
(element-children elm
)))))
682 (warn "The ~A element is not allowed with <SELECT>." (element-gi elm
))
685 (defun make-option-menu-replacement (device elm
)
686 (gui::make-option-menu
689 :options
(remove nil
(mapcar #'parse-options
(element-children elm
)))
690 :name
(pt-attr* elm
:name
)
691 :multiple-p
(pt-attr/boolean-flag elm
:multiple
)
692 :disabled-p
(pt-attr/boolean-flag elm
:disabled
)
693 :size
(pt-attr/integer elm
:size nil
)
694 :text-style
(pt-text-style device elm
)))
700 ;; <FORM action="..." method="post">
702 ;; <INPUT type="password" style="display:none"
703 ;; name="invisible-password"
704 ;; value="mypassword">
710 (defparameter *use-images-p
* t
)
712 (defun image-replaced-element (document device elm
)
713 (image-replaced-element/aux document device elm
714 :uri
(pt-effective-url-attr document elm
:src
)
715 :usemap
(pt-attr* elm
:usemap nil
)
716 :width
(pt-attr/pixels elm
:width nil
)
717 :height
(pt-attr/pixels elm
:height nil
)
718 :alt
(pt-attr* elm
:alt
(rod "[image]"))))
720 (defclass graphical-submit
()
721 ((name :initarg
:name
:reader gs-name
)
722 (pt :initarg
:pt
:reader gs-pt
)
725 (defmethod command-documentation ((self graphical-submit
))
728 (defclass server-side-image-map
()
729 ((url :initarg
:url
:reader ssim-url
)
730 (target :initarg
:target
:reader ssim-target
)
731 (title :initarg
:title
:reader ssim-title
)))
733 (defun find-A-element-for-img-ismap (img-element)
734 (do ((q img-element
(element-parent q
)))
736 (when (and (eq (element-gi q
) :a
)
737 (not (null (pt-attr* q
:href
))))
740 (defmethod command-documentation ((self server-side-image-map
))
741 (with-slots (title url
) self
742 (format nil
"Server side image map: ~A (~A)."
744 (url:unparse-url url
))))
746 (defun image-replaced-element/aux
(document device elm
747 &key uri
(usemap nil
) (width nil
) (height nil
)
749 (cond (*use-images-p
*
752 (usemap (and usemap
(url:parse-url
753 ;;xxx use something better than rod-string
754 (rod-string usemap
))))
756 (let ((obj (gui::make-image-replacement device document
:url url
:width w
:height h
))
762 (find-and-parse-image-map document usemap
)))
764 (when (pt-attr* elm
:ismap nil
)
766 (warn "ISMAP ignored, since client side image map is present."))
768 (let ((aelm (find-A-element-for-img-ismap elm
)))
770 (warn "Found <IMG ISMAP ..> without surounding A element."))
774 (or (pt-attr* elm
:alt nil
)
775 (pt-attr* aelm
:title nil
)
776 (pt-attr* aelm
:alt nil
))))
778 (list (make-instance 'imap-everywhere
779 :link
(make-instance 'server-side-image-map
780 :url
(pt-effective-url-attr document aelm
:href
)
781 :target
"_top" ;; xxx
782 :title title
)))))))))))
784 (setf map
(append map usemapmap
))
785 (setf (pt-%replacement elm
) (list obj nil
)) ;warum wird das gebraucht!?
788 (let ((r (sgml::copy-pt elm
)))
789 (setf (pt-%replacement r
) nil
)
790 (setf (element-children r
)
792 (sgml::make-pt
/low
:name
:pcdata
798 ;; wir haben das mit dem impliciten stil noch falsch gemacht:
800 ;; | 3.2 CASCADING ORDER
802 ;; | A declaration in the 'STYLE' attribute of an element (see section 1.1
803 ;; | for an example) has the same weight as a declaration with an ID-based
804 ;; | selector that is specified at the end of the style sheet:
806 ;; | <STYLE TYPE="text/css">
807 ;; | #x97z { color: blue }
810 ;; | <P ID=x97z STYLE="color: red">
812 ;; | In the above example, the color of the 'P' element would be red.
813 ;; | Although the specificity is the same for both declarations, the
814 ;; | declaration in the 'STYLE' attribute will override the one in the
815 ;; | 'STYLE' element because of cascading rule number 5.
817 ;; | The UA may choose to honor other stylistic HTML attributes, for
818 ;; | example 'ALIGN'. If so, these attributes are translated to the
819 ;; | corresponding CSS rules with specificity equal to 1. [*] The rules are
820 ;; | assumed to be at the start of the author style sheet and may be
821 ;; | overridden by subsequent style sheet rules. In a transition phase,
822 ;; | this policy will make it easier for stylistic attributes to coexist
823 ;; | with style sheets.
825 ;; [*] that is they should be treated like a selector featuring
827 ;; Note: This rule about the HTML attrs is bad. If say e.g.
829 ;; P { text-align: left; }, i have no option to say in the HTML
830 ;; document e.g. <P align=right> Arg!
835 ;; command-documentation command -> rod
836 ;; command-cursor command -> cursor keyword
839 (defstruct hyper-link
840 ;; structure for a hyper-link
841 url
;the URL this points to
842 alt
;alternate text or NIL
844 ;; wir brauchen noch:
845 ;; - is-map-p (für server-side image maps).
847 ;; und Möglichkeiten noch zu specifizieren, daß das hier eine FORM
849 ;; d.h. wir benötigen eher so etwas wie ein assoziertes Commando.
852 (defmethod command-documentation ((self hyper-link
))
853 (let ((url-string (if (not (null (hyper-link-url self
)))
854 (url:unparse-url
(hyper-link-url self
))
856 (if (hyper-link-alt self
)
857 (format nil
"~A (~A)"
858 (rod-string (hyper-link-alt self
)) url-string
)
859 (format nil
"~A" url-string
))))
861 (defmethod command-documentation ((self hyper-link
))
862 (let ((url-string (if (not (null (hyper-link-url self
)))
863 (url:unparse-url
(hyper-link-url self
))
865 (if (hyper-link-alt self
)
866 (concatenate 'rod
(hyper-link-alt self
)
868 (string-rod url-string
)
870 (format nil
"~A" url-string
))))
872 (defmethod command-documentation ((self t
))
874 (format nil
"~S" self
))
878 (defclass imap-area
()
879 ((link :initarg
:link
:initform nil
:reader imap-area-link
)));hyper link
881 (defclass imap-everywhere
(imap-area)
884 (defclass imap-rectangle
(imap-area)
890 (defclass imap-circle
(imap-area)
893 (radius :initarg
:radius
)) )
895 (defclass imap-polygon
(imap-area)
896 ((point-seq :initarg
:point-seq
))) ;sequence (x0 y0 x1 y1 .. xn yn) as in CLX
898 (defun area-node-to-imap-area (elm)
899 ;; Parses an AREA (or A) element into an imap-area object.
900 ;; Returns NIL, if something goes wrong.
901 (assert (member (element-gi elm
) '(:AREA
:A
)))
902 (let ((href (and (pt-attr* elm
:href nil
)
903 (url:parse-url
(pt-attr/latin1 elm
:href
))))
904 (nohref (pt-attr/boolean-flag elm
:nohref
))
905 (alt (pt-attr* elm
:alt nil
))
906 (shape (pt-attr/enum elm
:shape
:rect
'(:default
:rect
:circle
:poly
)))
907 (coords (pt-attr/length-list elm
:coords nil
))
908 (target (pt-effective-target-attr elm
:target
)))
909 (when (and nohref href
)
910 (warn "AREA element has HREF attribute despite of present NOHREF attribute.~
911 NOHREF takes precedence.")
914 (warn "Each AREA element should have an 'alt' attribute."))
915 (let ((link (make-hyper-link
920 ((:default
) (area-node-to-imap-area/default elm link coords
))
921 ((:rect
) (area-node-to-imap-area/rect elm link coords
))
922 ((:circle
) (area-node-to-imap-area/circle elm link coords
))
923 ((:poly
) (area-node-to-imap-area/poly elm link coords
))) )) )
925 (defun area-node-to-imap-area/default
(elm link coords
)
926 (declare (ignore elm
))
927 (cond ((= 0 (length coords
))
928 (make-instance 'imap-everywhere
931 (warn "A `default' AREA should not specify any `coords'.")
934 (defun area-node-to-imap-area/rect
(elm link coords
)
935 (declare (ignore elm
))
936 (cond ((= 4 (length coords
))
937 (make-instance 'imap-rectangle
942 :y1
(fourth coords
) ))
944 (warn "A `rect' AREA should specify exactly four `coords'.")
947 (defun area-node-to-imap-area/circle
(elm link coords
)
948 (declare (ignore elm
))
949 (cond ((= 3 (length coords
))
950 (make-instance 'imap-circle
954 :radius
(third coords
) ))
956 (warn "A `circle' AREA should specify exactly three `coords'.")
959 (defun area-node-to-imap-area/poly
(elm link coords
)
960 (declare (ignore elm
))
961 (cond ((evenp (length coords
))
962 (make-instance 'imap-polygon
966 (warn "A `poly' AREA should specify an even number of `coords'.")
969 (defun parse-image-map (document node
)
970 "Parses a <MAP> node into a list of IMAP-AREA objects"
971 ;; TODO: warning message, if old and new style <MAP>'s are mixed.
972 ;; for now we simply collect all AREA and A elements.
973 (declare (ignore document
))
974 (assert (eq (element-gi node
) :MAP
))
976 (sgml:map-pt
(lambda (x)
977 (when (member (element-gi x
) '(:A
:AREA
))
978 (let ((q (area-node-to-imap-area x
)))
982 ;; order is important
985 (defun find-image-map (document url
)
986 (cond ((not (null (url:url-anchor url
)))
987 (cond ((and (null (url:url-protocol url
))
988 (null (url:url-host url
))
989 (null (url:url-port url
))
990 (null (url:url-path url
))
991 (null (url:url-host url
)))
992 ;; something in this document
993 (find-image-map-in-pt (document-pt document
) (url:url-anchor url
)) )
995 (warn "Image maps in other documents than the current are not supported.")
998 (warn "Found `usemap' URI without an anchor component.")
1001 (defun find-image-map-in-pt (pt anchor
)
1002 (sgml:map-pt
(lambda (node)
1003 (when (and (eq (element-gi node
) :map
)
1004 (pt-attr* node
:name
)
1005 (string-equal (pt-attr/latin1 node
:name
) anchor
))
1006 (return-from find-image-map-in-pt node
)))
1009 ;; Questions with regard to image maps:
1010 ;; 1. What is the exact meaning of percentage values?
1011 ;; 2. Why does the A element have no "nohref" attribute. Accident?
1012 ;; 3. Why has AREA none of the rel, rev, charset etc. Attributes?
1016 (defmethod area-contains-point-p ((area imap-everywhere
) x y w h
)
1017 (declare (ignore x y w h
))
1020 (defmethod area-contains-point-p ((area imap-rectangle
) x y w h
)
1021 (with-slots (x0 y0 x1 y1
) area
1022 (let ((x0 (area-resolve-coordinate x0 w
))
1023 (y0 (area-resolve-coordinate y0 h
))
1024 (x1 (area-resolve-coordinate x1 w
))
1025 (y1 (area-resolve-coordinate y1 h
)))
1026 (and (<= (min x0 x1
) x
(max x0 x1
))
1027 (<= (min y0 y1
) y
(max y0 y1
))))))
1029 (defmethod area-contains-point-p ((area imap-circle
) px py w h
)
1030 (with-slots (x y radius
) area
1031 (let ((x (area-resolve-coordinate x w
))
1032 (y (area-resolve-coordinate y h
))
1033 (radius (min (area-resolve-coordinate radius w
)
1034 (area-resolve-coordinate radius h
))))
1035 (<= (+ (* (- px x
) (- px x
))
1036 (* (- py y
) (- py y
)))
1037 (* radius radius
)))))
1039 (defun polygon-contains-point-p (coord-seq x y
)
1040 (let ((n (floor (length coord-seq
) 2))
1042 (macrolet ((px (i) `(elt coord-seq
(* 2 (mod ,i n
))))
1043 (py (i) `(elt coord-seq
(+ 1 (* 2 (mod ,i n
))))))
1045 (let ((x0 (px i
)) (y0 (py i
))
1046 (x1 (px (+ i
1))) (y1 (py (+ i
1))))
1047 (cond ((or (< y0 y y1
)
1050 (cond ((= (- x1 x0
) 0) x0
)
1052 (* (+ (* (/ (- y1 y0
) (- x1 x0
)) x0
)
1054 (/ (- x1 x0
) (- y1 y0
)))))))
1055 (cond ((< xi x
) (incf k
))
1056 ((= xi x
) (return-from polygon-contains-point-p t
)))))
1057 ((and (/= y0 y
) (= y1 y
))
1058 (let ((j (do ((j (+ i
1) (+ j
1)))
1062 (when (/= (signum (- y y0
)) (signum (- y
(py j
))))
1063 (cond ((< x1 x
) (incf k
))
1064 ((= x1 x
) (return-from polygon-contains-point-p t
))))))
1065 ((and (= y0 y
) (= y
1 y
))
1066 (when (or (<= x0 x x1
) (>= x0 x x1
))
1067 (return-from polygon-contains-point-p t
))))))
1070 (defmethod area-contains-point-p ((area imap-polygon
) px py w h
)
1071 (let ((new-point-seq nil
))
1072 ;; resolve all coordinates and put then into points as list of conses
1073 (with-slots (point-seq) area
1074 (do ((q point-seq
(cddr q
)))
1075 ((or (endp q
) (endp (cdr q
))))
1076 (push (area-resolve-coordinate (cadr q
) h
) new-point-seq
)
1077 (push (area-resolve-coordinate (car q
) w
) new-point-seq
)))
1078 ;; the hard work is done in GU
1079 (polygon-contains-point-p new-point-seq px py
)))
1081 (defun area-resolve-coordinate (coordinate whole
)
1082 (ecase (car coordinate
)
1083 ((:PX
) (cdr coordinate
))
1084 ((:%
) (/ (* (cdr coordinate
) whole
) 100))))
1088 (defun find-and-parse-image-map (document url
)
1089 "Attemps to convert a URL to an image, return NIL, if anything fails."
1090 (let ((p (find-image-map document url
)))
1092 (setf p
(parse-image-map document p
)))
1094 (setf p
(mapcar (lambda (x)
1096 (when (hyper-link-url (slot-value x
'link
))
1097 (setf (hyper-link-url (slot-value x
'link
))
1098 (url:merge-url
(hyper-link-url (slot-value x
'link
))
1099 (document-base-url document
))))
1104 (defclass ro
/image
()
1105 ((url :initarg
:url
)
1106 (iwidth :initarg
:iwidth
)
1107 (iheight :initarg
:iheight
)
1108 (awidth :initarg
:awidth
:initform nil
)
1109 (aheight :initarg
:aheight
:initform nil
)
1111 (aimage :initarg
:aimage
:initform nil
)
1112 (pixmap :initform nil
)
1113 (mask :initform nil
)
1114 (fixed-size-p :initform nil
:initarg
:fixed-size-p
)
1117 (defmethod ro/intrinsic-size
((self ro
/image
))
1118 (with-slots (url iwidth iheight
) self
1119 (cond ((and iwidth iheight
)
1120 (values iwidth iheight
0))
1122 (values 20 20 0)))))
1124 (defmethod ro/size
((self ro
/image
))
1125 (with-slots (url awidth aheight
) self
1126 (cond ((and awidth aheight
)
1127 (values awidth aheight
0))
1129 (values 20 20 0)))))
1131 (defmethod update-lazy-object (document (self ro
/image
))
1132 (with-slots (url fixed-size-p
) self
1133 (let ((aim (document-fetch-image document self url
)))
1134 (with-slots (iwidth iheight
(self.aimage aimage
) awidth aheight
) self
1135 (setf iwidth
(aimage-width aim
)
1136 iheight
(aimage-height aim
)
1138 (unless awidth
(setf awidth
(aimage-width aim
)))
1139 (unless aheight
(setf aheight
(aimage-height aim
)))
1143 (with-slots (aimage awidth aheight
) self
1144 (let ((drawable (xlib:screen-root
(xlib:display-default-screen clue-gui2
::*dpy
*))))
1145 (with-slots (pixmap mask
) self
1147 (let ((q (clue-gui2::make-pixmap-from-aimage drawable aimage awidth aheight
)))
1148 (setf pixmap
(car q
)
1156 (defmethod x11-draw-robj (drawable gcontext
(self ro
/image
) box x y
)
1159 (with-slots (alt awidth aheight aimage url
) self
1161 (unless awidth
(setf awidth
(aimage-width aimage
)))
1162 (unless aheight
(setf aheight
(aimage-height aimage
)))
1163 (with-slots (pixmap mask
) self
1165 (warn "Rendering pixmap while redisplay (~S)"
1167 (let ((q (clue-gui2::make-pixmap-from-aimage drawable aimage awidth aheight
)))
1168 (setf pixmap
(car q
)
1170 (cond ((not (null mask
))
1171 (xlib:with-gcontext
(gcontext :clip-mask mask
1173 :clip-y
(- y aheight
))
1174 (xlib:copy-area pixmap gcontext
0 0 awidth aheight
1175 drawable x
(- y aheight
))) )
1177 (xlib:copy-area pixmap gcontext
0 0 awidth aheight
1178 drawable x
(- y aheight
) )))))
1181 (multiple-value-bind (w h
) (ro/size self
)
1184 (xlib:with-gcontext
(gcontext
1185 :foreground
(ws/x11
::x11-find-color drawable
:black
)
1187 (xlib:draw-glyphs drawable gcontext x y
(rod-string alt
))
1188 (xlib:draw-rectangle drawable gcontext x
(- y h
) w h
)))) )))
1191 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
1193 ;; Now this is misnomer, since being HTML is no property of the tree,
1194 ;; but of the "document language", we'll refine that later.
1197 (defmethod closure-protocol:element-css-class
((element sgml
::pt
))
1198 (element-attribute element
:CLASS
))
1200 (defmethod closure-protocol:element-css-id
((element sgml
::pt
))
1201 (element-attribute element
:ID
))
1203 (defmethod closure-protocol:pseudo-class-matches-p
(pseudo-class (pt sgml
::pt
))
1206 (and (not (null (r2::pt-attr
/latin1 pt
:href
))))) ;this is of course a bit too lazy!
1209 (sgml:pt-attr pt
:%hover-p nil
)))
1211 (eq (sgml::pt-attr pt
:%pseudo-class
) pseudo-class
))
1215 (defmethod closure-protocol:element-style-cache
((element sgml
::pt
))
1216 (sgml::pt-cache element
))
1218 (defmethod (setf closure-protocol
:element-style-cache
) (new-value (element sgml
::pt
))
1219 (setf (sgml::pt-cache element
) new-value
))
1223 (defmethod closure-protocol:element-base-url
((language html-document-language
) element
)
1224 (let ((base-element (pt-headers-base-element element
)))
1225 (cond ((and (not (null base-element
))
1226 (not (null (pt-attr* base-element
:HREF
))))
1227 (url:parse-url
(pt-attr/latin1 base-element
:HREF
)))
1231 (defun pt-headers-base-element (pt)
1232 "Return the <BASE> element of the document `pt' is a sub-node of."
1233 (cond ((null pt
) nil
)
1234 ((eq (element-gi pt
) :HTML
)
1235 (dolist (q (element-children pt
))
1236 (when (eq (element-gi q
) :HEAD
)
1237 (dolist (k (element-children q
))
1238 (when (eq (element-gi k
) :BASE
)
1239 (return-from pt-headers-base-element k
))))))
1240 ((pt-headers-base-element (element-parent pt
))) ))
1242 (defun pt-base-target (pt)
1243 ;; only used from CLUE-GUI and PT-EFFECTIVE-TARGET-ATTR, soon to be nuked.
1244 (let ((base-element (pt-headers-base-element pt
)))
1246 (pt-attr/latin1 base-element
:TARGET NIL
))))
1248 (defun pt-effective-target-attr (pt attr
)
1249 "Return the effective target attribute of `pt' named `attr';
1250 Does look at the header, if `pt' has no attribute called `attr'."
1251 (or (pt-attr/latin1 pt attr nil
)
1252 (pt-base-target pt
)))
1254 (defmethod closure-protocol:element-imap
((document-language html-document-language
)
1257 (cond ((and (eq (element-gi element
) :A
)
1258 (pt-attr* element
:href nil
))
1259 (make-instance 'imap-everywhere
1260 :link
(make-hyper-link
1261 :url
(pt-effective-url-attr document element
:href
)
1262 :alt
(pt-attr* element
:title nil
)
1263 :target
(pt-effective-target-attr element
:target
))))
1269 (defmethod closure-protocol:render
1270 ((document-language html-document-language
)
1273 &optional
(flag t
) (h 0)
1274 &key
(selected-style :default
))
1275 (declare (ignorable document-language
))
1277 ;; temporary medicine
1278 (setf (document-title document
) nil
1279 (document-links document
) nil
)
1281 ;; Post possible HTTP Link: header fields.
1282 (dolist (k (document-http-header document
))
1283 (cond ((string-equal :link
(car k
))
1284 (let ((links (netlib:parse-http-link-field
(cdr k
))))
1286 (warn "HTTP \"Link\" header field has illegal syntax: ~S." (cdr k
)))
1288 (dolist (link links
)
1289 (process-head-child document
(sgml:lhtml-
>pt link
)))))))))
1291 ;; First of all process the head
1292 (dolist (k (element-children pt
))
1293 (when (eq (element-gi k
) :head
)
1294 (process-head document k
)))
1297 (unless (document-title document
)
1298 (setf (document-title document
) "no title")
1299 (warn "Each document should have a TITLE element."))
1301 (render2 device document pt selected-style
)
1302 '(render-pt device document pt w flag h
:selected-style selected-style
)
1305 (defun process-head (document pt
)
1306 (dolist (k (element-children pt
))
1307 (process-head-child document k
)))
1309 (defun process-head-child (doc pt
)
1310 (cond ((eq (element-gi pt
) :title
)
1311 (setf (document-title doc
) (pt-data pt
)))
1312 ((eq :link
(element-gi pt
))
1313 (setf (document-links doc
)
1314 (append (document-links doc
)
1315 (list (make-instance 'link
1316 :title
(pt-attr/latin1 pt
:title
)
1317 :rel
(pt-attr/link-types pt
:rel
)
1318 :rev
(pt-attr/link-types pt
:rev
)
1319 :media
(pt-attr/latin1 pt
:media
) ;; xxx (comma-separated-list k :media)
1320 :target
(pt-attr/latin1 pt
:target
)
1321 :href
(pt-effective-url-attr doc pt
:href
))))))
1325 (defmethod closure-protocol:root-element-embedded-style
((language html-document-language
) pt
)
1326 (let ((style-node (the-style-node pt
)))
1328 (pt-all-data style-node
))))
1330 (defun br-element-p (element)
1331 (and (not (text-element-p element
))
1332 (eql :BR
(element-gi element
))))
1336 (defgeneric element-attribute-parser
(document-language gi attribute
))
1338 (defun parsed-attribute (document-language element attribute
)
1339 (funcall (element-attribute-parser document-language
(element-gi element
) attribute
)
1340 (element-attribute element attribute
)))
1344 (defgeneric eis
(document-language element
)
1345 (:method-combination append
))
1347 (defgeneric eis-by-gi
(document-language element gi
)
1348 (:method-combination append
))
1350 (defgeneric eis-by-attr
(document-language element gi attr value
)
1351 (:method-combination append
))
1353 (defmethod eis append
(document-language element
)
1354 ;; fallback returning NIL
1357 (defmethod eis :around
(document-language element
)
1359 (eis-by-gi document-language element
(element-gi element
))
1360 (call-next-method)))
1362 (defmethod eis-by-gi :around
(document-language element gi
)
1364 (loop for
(attr value . rest
) = (sgml::pt-attrs element
) then rest while attr
1366 (eis-by-attr document-language element
1367 (element-gi element
)
1370 (call-next-method)))
1372 (defmethod eis-by-attr append
(document-language element gi attr value
)
1375 (defmethod eis-by-gi append
(document-language element gi
)
1378 (defmacro define-style-mapping
(document-language (element &optional gi attr value
) &body body
)
1379 (cond ((and (consp gi
)
1381 (eq (car (cadr gi
)) 'member
))
1383 ,@(mapcar (lambda (elt)
1384 `(define-style-mapping ,document-language
1385 (,element
(,(car gi
) (eql ,elt
)) ,attr
,value
)
1389 `(defmethod ,(cond (value 'eis-by-attr
)
1393 ((document-language ,document-language
)
1396 ,@(and gi attr value
`(,attr
,value
)))
1397 (declare (ignorable document-language
,element
1398 ,@(and gi
(list (car gi
)))
1399 ,@(and attr
(list (car attr
)))
1400 ,@(and value
(list value
))))
1402 (labels ((add-style (attr value
)
1403 (push (cons attr value
) res
)))
1409 (defmethod closure-protocol:element-implicit-style
(document (element sgml
::pt
))
1410 (element-implicit-style-2 *document-language
* document element
))
1412 (defun element-implicit-style-2 (document-language document element
)
1413 (declare (ignore document
))
1414 (eis document-language element
))
1416 (defun implicit-style/IMG
(pt)
1418 (case (pt-attr/img-align pt
:align nil
)
1419 (:top
(push (cons 'css
:@vertical-align
:top
) res
))
1420 (:bottom
(push (cons 'css
:@vertical-align
:baseline
) res
)) ;wrong?
1421 (:middle
(push (cons 'css
:@vertical-align
:img-middle
) res
))
1422 (:left
(push (cons 'css
:@float
:left
) res
))
1423 (:right
(push (cons 'css
:@float
:right
) res
)))
1424 (when (setf x
(pt-attr/pixels pt
:vspace nil
))
1425 (push (cons 'css
:@margin-top
(cons :px x
)) res
)
1426 (push (cons 'css
:@margin-bottom
(cons :px x
)) res
))
1427 (when (setf x
(pt-attr/pixels pt
:hspace nil
))
1428 (push (cons 'css
:@margin-left
(cons :px x
)) res
)
1429 (push (cons 'css
:@margin-right
(cons :px x
)) res
))
1431 (when (setf x
(pt-attr/pixels pt
:border nil
))
1432 (push (cons 'css
:@border-left-width
(cons :px x
)) res
)
1433 (push (cons 'css
:@border-right-width
(cons :px x
)) res
)
1434 (push (cons 'css
:@border-top-width
(cons :px x
)) res
)
1435 (push (cons 'css
:@border-bottom-width
(cons :px x
)) res
))
1437 (when (setf x
(pt-attr/length pt
:width nil
))
1438 (push (cons 'css
:@width x
) res
))
1439 (when (setf x
(pt-attr/length pt
:height nil
))
1440 (cond ((css:percentage-p x
)
1441 ;; REC-html40-19980424, 13.7.1 says:
1442 ;; | [...] that lengths expressed as percentages are based on the
1443 ;; | [...] vertical space currently available
1444 ;; without defining that "vertical space currently available"
1445 (warn "No percentage values on IMG.height please."))
1447 (push (cons 'css
:@height x
) res
))))
1450 (defmacro define-simple-style
(document-language (gi attr value
) (css-attr css-value
))
1451 `(define-style-mapping ,document-language
(element (gi (eql ,gi
)) (attr (eql ,attr
)) (value (eql ,value
)))
1452 (declare (ignore value
))
1453 (add-style ',css-attr
',css-value
)))
1457 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :ALIGN
)) value
)
1458 (case (pt-attr/img-align element
:align nil
)
1459 (:top
(add-style 'css
:@vertical-align
:top
))
1460 (:bottom
(add-style 'css
:@vertical-align
:baseline
)) ;wrong?
1461 (:middle
(add-style 'css
:@vertical-align
:img-middle
))
1462 (:left
(add-style 'css
:@float
:left
))
1463 (:right
(add-style 'css
:@float
:right
))))
1465 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :VSPACE
)) value
)
1467 (when (setf x
(pt-attr/pixels element
:vspace nil
))
1468 (add-style 'css
:@margin-top
(cons :px x
))
1469 (add-style 'css
:@margin-bottom
(cons :px x
)))))
1471 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :HSPACE
)) value
)
1473 (when (setf x
(pt-attr/pixels element
:hspace nil
))
1474 (add-style 'css
:@margin-left
(cons :px x
))
1475 (add-style 'css
:@margin-right
(cons :px x
)))))
1477 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :BORDER
)) value
)
1479 (when (setf x
(pt-attr/pixels element
:border nil
))
1480 (add-style 'css
:@border-left-width
(cons :px x
))
1481 (add-style 'css
:@border-right-width
(cons :px x
))
1482 (add-style 'css
:@border-top-width
(cons :px x
))
1483 (add-style 'css
:@border-bottom-width
(cons :px x
)))))
1485 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :WIDTH
)) value
)
1487 (when (setf x
(pt-attr/length element
:width nil
))
1488 (add-style 'css
:@width x
))))
1490 (define-style-mapping html-4.0-document-language
(element (gi (eql :IMG
)) (attr (eql :HEIGHT
)) value
)
1492 (when (setf x
(pt-attr/length element
:height nil
))
1493 (cond ((css:percentage-p x
)
1494 ;; REC-html40-19980424, 13.7.1 says:
1495 ;; | [...] that lengths expressed as percentages are based on the
1496 ;; | [...] vertical space currently available
1497 ;; without defining that "vertical space currently available"
1498 (warn "No percentage values on IMG.height please."))
1500 (add-style 'css
:@height x
))))))
1504 (define-style-mapping html-4.0-document-language
(element (gi (member :UL
:OL
:LI
)) (attr (eql :TYPE
)) value
)
1505 (let ((x (pt-attr/list-style-type element
:type
)))
1506 (and x
(add-style 'css
:@list-style-type x
))))
1508 (define-style-mapping html-4.0-document-language
(element (gi (member :UL
:OL
)) (attr (eql :COMPACT
)) value
)
1509 (when value
(add-style 'css
:@list-style-position
:inset
)))
1514 (define-style-mapping html-4.0-document-language
(element
1515 (gi (member :P
:DIV
:H1
:H2
:H3
:H4
:H5
:H6
))
1518 (let ((x (pt-attr/align element
)))
1519 (and x
(add-style 'css
:@text-align x
))))
1523 (define-style-mapping html-4.0-document-language
(element (gi (eql :BR
)) (attr (eql :clear
)) value
)
1524 (case (pt-attr/clear element
)
1525 ((:left
) (add-style 'css
:@clear
:left
))
1526 ((:right
) (add-style 'css
:@clear
:right
))
1527 ((:all
) (add-style 'css
:@clear
:both
))))
1531 (define-style-mapping html-4.0-document-language
(pt (gi (eql :FONT
)))
1533 (when (pt-attr/color pt
:color
)
1534 (add-style 'css
:@color
(pt-attr/color pt
:color
)))
1535 (when (pt-attr/latin1 pt
:face
)
1536 (let ((f (split-by-if (lambda (x)
1537 (member x
(list #\
, #\space
#\tab
#\newline
#\return
)
1539 (pt-attr/latin1 pt
:face
) :nuke-empty-p t
)))
1540 (add-style 'css
:@font-family f
)))
1541 (when (setq sz
(maybe-parse-integer (pt-attr/latin1 pt
:size
)))
1542 (cond ((or (char= (char (pt-attr/latin1 pt
:size
) 0) #\
+)
1543 (char= (char (pt-attr/latin1 pt
:size
) 0) #\-
))
1544 ;;(add-style 'css:@font-size (cons :% (* 100 (expt 7/6 sz))))
1545 (add-style 'css
:@font-size
(cons :px
(* 14 (expt 7/6 sz
)))))
1547 (add-style 'css
:@font-size
1548 (cond ((<= sz
0) (cons :pt
6))
1549 ((= sz
1) (cons :pt
8))
1550 ((= sz
2) (cons :pt
10))
1551 ((= sz
3) (cons :pt
12))
1552 ((= sz
4) (cons :pt
14))
1553 ((= sz
5) (cons :pt
16))
1554 ((= sz
6) (cons :pt
18))
1555 ((>= sz
7) (cons :pt
20))))) )) ))
1559 (define-style-mapping html-4.0-document-language
(pt (gi (eql :TR
)))
1561 (when (member (setq x
(pt-attr/cell-halign pt
:align
)) '(:left
:center
:right
:justify
))
1562 (add-style 'css
:@text-align x
))))
1566 (define-style-mapping html-4.0-document-language
(pt (gi (eql :HR
)))
1567 (let ((noshade (pt-attr/boolean-flag pt
:noshade
))
1568 (size (pt-attr/pixels pt
:size
))
1569 (width (pt-attr/length pt
:width
))
1570 (align (pt-attr/align pt
:align
)))
1572 (add-style 'css
:@border-top-style
:solid
)
1573 (add-style 'css
:@border-right-style
:solid
)
1574 (add-style 'css
:@border-bottom-style
:solid
)
1575 (add-style 'css
:@border-left-style
:solid
))
1577 (add-style 'css
:@width width
))
1579 (add-style 'css
:@border-top-width
(cons :px
(floor size
2)))
1580 (add-style 'css
:@border-bottom-width
(cons :px
(ceiling size
2))))
1583 (add-style 'css
:@margin-left
'(:px .
0))
1584 (add-style 'css
:@margin-right
:auto
))
1586 (add-style 'css
:@margin-left
:auto
)
1587 (add-style 'css
:@margin-right
'(:px .
0)))
1589 (add-style 'css
:@margin-left
:auto
)
1590 (add-style 'css
:@margin-right
:auto
))) ))
1594 (define-style-mapping html-4.0-document-language
(pt (gi (member :TD
:TH
)))
1596 (when (pt-attr/color
(element-parent pt
) :bgcolor
)
1597 (add-style 'css
:@background-color
(pt-attr/color
(element-parent pt
) :bgcolor
)))
1598 (when (pt-attr/color pt
:bgcolor
)
1599 (add-style 'css
:@background-color
(pt-attr/color pt
:bgcolor
)))
1600 (when (pt-attr/boolean-flag pt
:nowrap
)
1601 (progn;;unless (pt-attr* pt :width) ;emulate netscape behaviour.
1602 (add-style 'css
:@white-space
:nowrap
)))
1603 (when (pt-attr/latin1 pt
:background
)
1604 (add-style 'css
:@background-image
(pt-effective-url-attr *document
* pt
:background
)))
1605 ;; The valign attribute is inherited
1606 ;; ### we miss inheriting valign from the column and column-group elements
1607 ;; Which is not that easy to gather from here, since for that
1608 ;; we actually are forced to parse the table structure, which
1609 ;; we do in the CSS renderer already.
1610 (when (setq x
(or (pt-attr/cell-valign pt
:valign
)
1611 (pt-attr/cell-valign
(element-parent pt
) :valign
) ;row
1612 (pt-attr/cell-valign
(element-parent (element-parent pt
)) :valign
) ;row-group
1613 (pt-attr/cell-valign
(element-parent (element-parent (element-parent pt
))) :valign
) ;table
1614 :middle
)) ;note that the default is not actually specified
1615 (add-style 'css
:@vertical-align x
))
1616 ;; ### we miss inheriting align from the column and column-group elements
1618 (when (member (setf x
(or (pt-attr/cell-halign pt
:align
)
1619 (pt-attr/cell-halign
(element-parent pt
) :align
) ;row
1620 (pt-attr/cell-halign
(element-parent (element-parent pt
)) :align
) ;row-group
1621 (pt-attr/cell-halign
(element-parent (element-parent (element-parent pt
))) :align
) ;table
1623 '(:left
:center
:right
:justify
))
1624 (add-style 'css
:@text-align x
))
1626 (when (setq x
(pt-attr/length pt
:width nil
))
1627 (when (css:percentage-p x
)
1628 (setf x
(cons :%
(cdr x
))))
1629 (add-style 'css
:@width x
))
1631 ;; ### we miss height.
1633 (let ((table (element-parent (element-parent (element-parent pt
)))))
1634 (when (and (eq (element-gi table
) :table
))
1635 (when (setq x
(pt-attr/length table
:cellpadding
))
1636 (add-style 'css
:@padding-top x
)
1637 (add-style 'css
:@padding-bottom x
)
1638 (add-style 'css
:@padding-left x
)
1639 (add-style 'css
:@padding-right x
))
1641 (add-style 'css
:@border-top-width
0)
1642 (add-style 'css
:@border-bottom-width
0)
1643 (add-style 'css
:@border-left-width
0)
1644 (add-style 'css
:@border-right-width
0)
1645 (add-style 'css
:@border-top-style
:none
)
1646 (add-style 'css
:@border-bottom-style
:none
)
1647 (add-style 'css
:@border-left-style
:none
)
1648 (add-style 'css
:@border-right-style
:none
)
1649 (when (equal '(:px .
0) (pt-attr/length table
:border
'(:px .
0)))
1650 (add-style 'css
:@border-top-width
0)
1651 (add-style 'css
:@border-bottom-width
0)
1652 (add-style 'css
:@border-left-width
0)
1653 (add-style 'css
:@border-right-width
0)) ))))
1657 (define-style-mapping html-4.0-document-language
(pt (gi (eql :TABLE
)))
1659 (when (eq (pt-attr/table.align pt
:align nil
) :left
)
1660 (add-style 'css
:@float
:left
))
1661 (when (eq (pt-attr/table.align pt
:align nil
) :right
)
1662 (add-style 'css
:@float
:right
))
1664 (when (eq (css:style- attr
(element-parent pt
) 'css
:@text-align
)
1666 (add-style 'css
:@margin-left
:auto
)
1667 (add-style 'css
:@margin-right
0))
1670 (when (or (eq (pt-attr/table.align pt
:align nil
) :center
)
1671 (eq (css:style- attr
(element-parent pt
) 'css
:@text-align
)
1673 (add-style 'css
:@margin-left
:auto
)
1674 (add-style 'css
:@margin-right
:auto
))
1676 (when (setq x
(pt-attr/length pt
:cellspacing nil
))
1677 (add-style 'css
::@border-spacing
(list x x
)))
1679 (when (setq x
(pt-attr/length pt
:width nil
))
1680 '(when (css:percentage-p x
)
1681 (setf x
(cons :canvas-h-percentage
(cdr x
))))
1682 (when (css:percentage-p x
)
1683 (setf x
(cons :%
(cdr x
))))
1684 (add-style 'css
:@width x
))
1685 (when (setq x
(pt-attr/color pt
:bgcolor
))
1686 (add-style 'css
:@background-color x
))))
1690 (define-style-mapping html-4.0-document-language
(pt (gi (eql :BODY
)))
1692 (when (pt-attr/latin1 pt
:background
)
1693 (add-style 'css
:@background-image
(pt-effective-url-attr *document
* pt
:background
)))
1694 (when (setq x
(pt-attr/color pt
:bgcolor
))
1695 (add-style 'css
:@background-color x
))
1696 (when (setq x
(pt-attr/color pt
:text
))
1697 (add-style 'css
:@color x
))))
1699 ;;;; THEAD/TBODY/TFOOT
1701 (define-style-mapping html-4.0-document-language
(pt (gi (member :THEAD
:TBODY
:TFOOT
)))
1703 ;; Note: align=justify is in the HTML spec but utter nonsense
1704 (when (member (setq x
(pt-attr/cell-halign pt
:align
))
1705 '(:left
:center
:right
:justify
))
1706 (add-style 'css
:@text-align x
))))
1710 (define-style-mapping html-4.0-document-language
(pt (gi (eql :A
)))
1712 ;; ### Now, this is crude!
1713 (when (and (pt-attr/latin1 pt
:href
)
1714 (setq x
(pt-body-element pt
))
1715 (setq x
(pt-attr/color x
:link
)))
1716 (add-style 'css
:@color x
))))
1720 (define-style-mapping html-4.0-document-language
(pt (gi (eql :INPUT
)))
1721 (when (eq :hidden
(pt-attr/input-type pt
:type
))
1722 (add-style 'css
:@display
:none
))
1723 (when (eq :image
(pt-attr/input-type pt
:type
))
1724 ;; ### - netscape 4 doesn't do this.
1725 (when (pt-attr/pixels pt
:size
)
1726 (add-style 'css
:@width
(cons :px
(pt-attr/pixels pt
:size
))))))