Patch by Christophe Rhodes on closure-devel <87ejk2sngi.fsf@cantab.net>
[closure-html.git] / src / renderer / renderer2.lisp
blob5f755ea9793ddeb2d4b3821a681c46f7277752ee
1 ;;; -*- Mode: Lisp; Syntax: Common-Lisp; Package: R2; -*-
2 ;;; ---------------------------------------------------------------------------
3 ;;; Title: The Core of the Renderer
4 ;;; Created: somewhen late 2002
5 ;;; Author: Gilbert Baumann <gilbert@base-engineering.com>
6 ;;; License: MIT style (see below)
7 ;;; $Id: renderer2.lisp,v 1.20 2007-07-01 12:16:44 dlichteblau Exp $
8 ;;; ---------------------------------------------------------------------------
9 ;;; (c) copyright 1997-2003 by Gilbert Baumann
11 ;;; Permission is hereby granted, free of charge, to any person obtaining
12 ;;; a copy of this software and associated documentation files (the
13 ;;; "Software"), to deal in the Software without restriction, including
14 ;;; without limitation the rights to use, copy, modify, merge, publish,
15 ;;; distribute, sublicense, and/or sell copies of the Software, and to
16 ;;; permit persons to whom the Software is furnished to do so, subject to
17 ;;; the following conditions:
18 ;;;
19 ;;; The above copyright notice and this permission notice shall be
20 ;;; included in all copies or substantial portions of the Software.
21 ;;;
22 ;;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 ;;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 ;;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
25 ;;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
26 ;;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
27 ;;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
28 ;;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 (in-package :R2)
32 ;;;; --------------------------------------------------------------------------------
34 ;;;; Upon Incremental Changes
36 ;; When something via :hover or DHTML changes, we can do it the
37 ;; following way:
39 ;; first find the set of all elements affected. that are all
40 ;; elements directly affected plus possible other elements affected
41 ;; because of geometry of things changed.
43 ;; we need to keep a kind of cache of the rendering state for each
44 ;; element or more precisely some sort of callback which will
45 ;; rerender a given element.
48 ;; We can center all this around paragraphs again. A to be rendered
49 ;; block box maps into a series of chunks, these chunks then map into
50 ;; a series of lines. These lines are then drawn. A certain change of
51 ;; an CSS rule results into possible changes at different levels and
52 ;; different information can be retained.
54 ;; The correct approach to this would be (and we once did that) to
55 ;; formulate rendering in a functional fashion and use pretty vanilla
56 ;; memo techniques. The hard part is to determine the exact set of CSS
57 ;; properties which affect a certain stage of rendering.
59 ;; We can start low and try to cache on a paragraph per paragraph
60 ;; basis. => Each paragraph will be another output record.
62 ;; When we keep a cache which maps block elements to their surrounding
63 ;; rc, a mere A:hover we can handled by just finding the nearest
64 ;; parent box element and just rerender that.
66 ;; Or put the otherway round: Each element produces some output, this
67 ;; output is retained in the output history together with the
68 ;; appropriate rcontext object. Unless there is something in it which
69 ;; changed or the rcontext changed we can keep the output history.
71 ;; That is we build a hierachy of output records each containing:
72 ;; a. A description of the surrounding state
73 ;; b. A closure callable to regenerate the output.
75 ;; This implies that we generally should change state as less as
76 ;; possible and that using iteration is "interesting".
78 (eval-when (:compile-toplevel :load-toplevel :execute)
79 (ignore-errors
80 (import '(CSS::COOKED-STYLE-BORDER-TOP-STYLE
81 CSS::COOKED-STYLE-PADDING-LEFT
82 CSS::COOKED-STYLE-TEXT-TRANSFORM
83 CSS::COOKED-STYLE-MARGIN-BOTTOM
84 CSS::COOKED-STYLE-FONT-VARIANT
85 CSS::COOKED-STYLE-POSITION
86 CSS::COOKED-STYLE-LINE-HEIGHT
87 CSS::COOKED-STYLE-FONT-WEIGHT
88 CSS::COOKED-STYLE-CONTENT
89 CSS::COOKED-STYLE-MARGIN-TOP
90 CSS::COOKED-STYLE-FLOAT
91 CSS::COOKED-STYLE-BACKGROUND-ATTACHMENT
92 CSS::COOKED-STYLE-LEFT
93 CSS::COOKED-STYLE-BACKGROUND-POSITION
94 CSS::COOKED-STYLE-BACKGROUND-IMAGE
95 CSS::COOKED-STYLE-BORDER-BOTTOM-WIDTH
96 CSS::COOKED-STYLE-BACKGROUND-REPEAT
97 CSS::COOKED-STYLE-HEIGHT
98 CSS::COOKED-STYLE-VERTICAL-ALIGN
99 CSS::COOKED-STYLE-COUNTER-INCREMENT
100 CSS::COOKED-STYLE-MARGIN-LEFT
101 CSS::COOKED-STYLE-TEXT-DECORATION
102 CSS::COOKED-STYLE-MARGIN-RIGHT
103 CSS::COOKED-STYLE-BORDER-BOTTOM-STYLE
104 CSS::COOKED-STYLE-BORDER-RIGHT-WIDTH
105 CSS::COOKED-STYLE-FONT-FAMILY
106 CSS::COOKED-STYLE-MARKER-OFFSET
107 CSS::COOKED-STYLE-LETTER-SPACING
108 CSS::COOKED-STYLE-BORDER-LEFT-COLOR
109 CSS::COOKED-STYLE-BORDER-TOP-WIDTH
110 CSS::COOKED-STYLE-BORDER-LEFT-STYLE
111 CSS::COOKED-STYLE-CLEAR
112 CSS::COOKED-STYLE-QUOTES
113 CSS::COOKED-STYLE-DISPLAY
114 CSS::COOKED-STYLE-LIST-STYLE-IMAGE
115 CSS::COOKED-STYLE-ORIG-WIDTH
116 CSS::COOKED-STYLE-COLOR
117 CSS::COOKED-STYLE-BORDER-BOTTOM-COLOR
118 CSS::COOKED-STYLE-WHITE-SPACE
119 CSS::COOKED-STYLE-BORDER-RIGHT-COLOR
120 CSS::COOKED-STYLE-PADDING-TOP
121 CSS::COOKED-STYLE-FONT-SIZE
122 CSS::COOKED-STYLE-BACKGROUND-COLOR
123 CSS::COOKED-STYLE-BORDER-LEFT-WIDTH
124 CSS::COOKED-STYLE-LIST-STYLE-TYPE
125 CSS::COOKED-STYLE-WORD-SPACING
126 CSS::COOKED-STYLE-BOTTOM
127 CSS::COOKED-STYLE-OVERFLOW
128 CSS::COOKED-STYLE-PADDING-BOTTOM
129 CSS::COOKED-STYLE-FONT-STYLE
130 CSS::COOKED-STYLE-CLIP
131 CSS::COOKED-STYLE-BORDER-RIGHT-STYLE
132 CSS::COOKED-STYLE-TEXT-INDENT
133 CSS::COOKED-STYLE-WIDTH
134 CSS::COOKED-STYLE-TOP
135 CSS::COOKED-STYLE-Z-INDEX
136 CSS::COOKED-STYLE-BORDER-TOP-COLOR
137 CSS::COOKED-STYLE-RIGHT
138 CSS::COOKED-STYLE-LIST-STYLE-POSITION
139 CSS::COOKED-STYLE-TEXT-ALIGN
140 CSS::COOKED-STYLE-PADDING-RIGHT
141 CSS::COOKED-STYLE-COUNTER-RESET
142 CSS::COOKED-STYLE-BORDER-SPACING))))
144 ;;;;
146 (defparameter *hyphenate-p* nil
147 "Whether we hyphenate.")
149 (defparameter *tex-mode-p* nil)
151 (defvar *canvas-width*)
152 (defvar *document-height* nil
153 "XXX variable to pass the document height back to the GUI; mostly because OR is broken.")
155 ;;;;
157 (defmacro defclass* (name super &rest slots)
158 (cond ((member name '(black-chunk))
159 `(progn
160 (defstruct (,name (:constructor
161 ,(intern
162 (with-standard-io-syntax
163 (format nil "CONS-~A" name)))
164 (&key ,@(mapcar (lambda (slot)
165 (let ((slot (if (consp slot) (car slot) slot)))
166 slot))
167 slots))))
168 ,@(mapcar (lambda (slot)
169 (let ((slot (if (consp slot) (car slot) slot))
170 (opts (if (consp slot) (cdr slot) nil)))
171 (list slot
172 (getf (if (consp slot) (cdr slot) nil) :initform))))
173 slots))
177 `(progn
178 (defclass ,name ,super
179 ,(mapcar (lambda (slot)
180 (let ((slot (if (consp slot) (car slot) slot))
181 (opts (if (consp slot) (cdr slot) nil)))
182 (cons slot
183 (append opts
184 (list :initarg (intern (symbol-name slot) :keyword))
185 ;; emarsden2003-03-12
186 (unless (member :initform opts) (list :initform nil))
187 (list :accessor (intern
188 (with-standard-io-syntax
189 (format nil "~A-~A" name slot))))))))
190 slots))
192 (defun ,(intern
193 (with-standard-io-syntax
194 (format nil "CONS-~A" name)))
195 (&rest args)
196 (apply #'make-instance ',name args))
198 (defun , (intern
199 (with-standard-io-syntax
200 (format nil "~A-P" name)))
201 (object)
202 (typep object ',name))
204 (defun ,(intern
205 (with-standard-io-syntax
206 (format nil "~A-MODIF" name)))
207 (.object. &key ,@(mapcar (lambda (slot)
208 (let ((slot (if (consp slot) (car slot) slot)))
209 (list slot nil (intern
210 (with-standard-io-syntax
211 (format nil ".P.~A" slot))))))
212 slots))
213 (make-instance ',name
214 ,@(mapcan (lambda (slot)
215 (let ((slot (if (consp slot) (car slot) slot)))
216 (list (intern (symbol-name slot) :keyword)
217 `(if ,(intern
218 (with-standard-io-syntax
219 (format nil ".P.~A" slot)))
220 ,slot
221 (slot-value .object. ',slot)))))
222 slots))))) ))
225 (defvar *device*)
226 (defvar *document*)
227 (defvar *style-sheet*)
229 (defvar *baseline* nil
230 "Kludge: When a line is actually rendered this variable unless
231 already set is set to the device y coordinate of the baseline. This is
232 used by the table renderer.")
234 (defclass* block-box ()
235 ;; a block box
236 style
237 content ;a list of other block, para, or marker boxen
238 element
239 content-output-record ;output record of content
240 decoration-output-record ;output record of decoration
241 output-record) ;overall output record
243 (defclass* para-box ()
244 items ;the main content of this paragraph
245 ; (a list of chunks)
246 output-record
247 genesis
250 (defclass* bounding-chunk ()
251 ;; common superclass for open/close chunks
252 halfp ;whether this is a half one
253 style ;its style
254 pt) ;its element
256 (defclass* open-chunk (bounding-chunk)
257 height
258 depth
261 (defclass* close-chunk (bounding-chunk)
264 (defclass* black-chunk ()
265 style
266 data
267 %width)
269 (defclass* replaced-object-chunk ()
270 element
271 object)
273 (defclass* disc-chunk ()
274 %before
275 %after
276 %here
277 forcep) ;whether this is a forced line break
278 ; (in which case 'here' may not apply)
280 (defclass* floating-chunk ()
281 style ;always the same as the (block-box-style (floating-chunk-content .))
282 content ;always a single block-box
283 %width ;width cache [outer width]
284 x1 y1 x2 y2) ;the bounding rectangle of this floating box.
285 ; (applicable if mounted).
288 (defmethod print-object ((object black-chunk) stream)
289 (format stream "#{~S}" (rod-string (black-chunk-data object))))
291 (defmethod print-object ((object bounding-chunk) stream)
292 (format stream "#{~A}" (chunk-debug-name object)))
294 (defmethod print-object ((object disc-chunk) stream)
295 (format stream "#{disk:~I~_here=~S ~_before=~S ~_after=~S}"
296 (disc-chunk-%here object)
297 (disc-chunk-%before object)
298 (disc-chunk-%after object)))
300 (defmethod print-object ((object block-box) stream)
301 (format stream "#<~:2I~S ~S~{ ~_~S~}>"
302 (type-of object)
303 (element-gi (block-box-element object))
304 (block-box-content object) ))
306 (defclass* kern-chunk ()
307 amount)
309 (defclass* text-indent-chunk (kern-chunk)
310 amount)
312 (defun text-indent-chunk-p (object)
313 (typep object 'text-indent-chunk))
315 (defun kern-chunk-p (x)
316 (typep x 'kern-chunk))
318 (defmethod chunk-width ((x kern-chunk))
319 (slot-value x 'amount))
321 (defun make-kern-chunk (amount)
322 (make-instance 'kern-chunk :amount amount))
324 (defun make-text-indent-chunk (amount)
325 (make-instance 'text-indent-chunk :amount amount))
327 (defmethod print-object ((object kern-chunk) stream)
328 (with-slots (amount) object
329 (format stream "#<kern:~D>" amount)))
331 (defmethod disc-chunk-before (chunk)
332 (let ((b (disc-chunk-%before chunk)))
333 (if (functionp b)
334 (setf (disc-chunk-%before chunk) (funcall b))
335 b)))
337 (defmethod disc-chunk-after (chunk)
338 (let ((b (disc-chunk-%after chunk)))
339 (if (functionp b)
340 (setf (disc-chunk-%after chunk) (funcall b))
341 b)))
343 (defmethod disc-chunk-here (chunk)
344 (let ((b (disc-chunk-%here chunk)))
345 (if (functionp b)
346 (setf (disc-chunk-%here chunk) (funcall b))
347 b)))
349 (defmethod chunk-width ((chunk open-chunk))
350 (if (bounding-chunk-halfp chunk)
352 (let ((style (bounding-chunk-style chunk)))
353 (+ (cooked-style-border-left-width style)
354 (cooked-style-padding-left style)
355 (cooked-style-margin-left style)))))
357 (defmethod chunk-width ((chunk close-chunk))
358 (if (bounding-chunk-halfp chunk)
360 (let ((style (bounding-chunk-style chunk)))
361 (+ (cooked-style-border-right-width style)
362 (cooked-style-padding-right style)
363 (cooked-style-margin-right style)))))
365 (defmethod chunk-width ((chunk black-chunk))
366 (declare (type black-chunk chunk))
367 (or (black-chunk-%width chunk)
368 (setf (black-chunk-%width chunk)
369 (let* ((style (black-chunk-style chunk))
370 (fn (text-style-font (make-text-style-from-cooked-style style))))
371 (loop for c across (black-chunk-data chunk)
372 sum (rune-width fn c)) ))))
374 (defmethod chunk-width ((chunk replaced-object-chunk))
375 (ro/size (replaced-object-chunk-object chunk)))
377 ;;;; Rendering (or dumping) a single line.
379 (defclass* line-fragment ()
380 x1 x2 ;left and right margin
381 block-style ;the style of the corresponding block
382 chunks) ;their chunks
384 (defparameter *debug-tags* nil)
385 (defparameter *debug-tables* nil)
387 (defun chunk-debug-name (chunk)
388 (format nil (if (bounding-chunk-halfp chunk)
389 "~(<~A~A>~)~A"
390 "<~A~A>~A")
391 (if (typep chunk 'close-chunk) "/" "")
392 (element-gi (bounding-chunk-pt chunk))
393 (typecase (bounding-chunk-pt chunk)
394 (before-pseudo-element ":before")
395 (after-pseudo-element ":after")
396 (first-line-pseudo-element ":first-line")
397 (first-letter-pseudo-element ":first-letter")
398 (t ""))))
400 (defclass link-presentation (CLIM:STANDARD-PRESENTATION)
403 #+NIL
404 (defmethod clim:highlight-output-record ((record link-presentation)
405 stream
406 mode)
407 (let ((object (clim:presentation-object record)))
408 ;;(tata mode)
411 (defun render-line (y fragments &aux (*package* (find-package "RENDERER")))
412 ;; The very first thing we need to do is to care for vertical align.
413 (multiple-value-bind (height depth) (vertically-align-line fragments)
414 (incf y height)
415 (unless *baseline* (setf *baseline* y)) ;this is for the table renderer
416 ;; and yet another time traversing stuff.
417 (dotimes (pass 2)
418 (dolist (frag fragments)
419 (let ((ss (list (line-fragment-block-style frag)))
420 (x (case (cooked-style-text-align (line-fragment-block-style frag))
421 ;; ### is this correct?
422 (:right
423 (- (line-fragment-x2 frag)
424 (reduce #'+ (mapcar #'chunk-width (line-fragment-chunks frag)))))
425 (:center
426 (/ (+ (line-fragment-x1 frag) (line-fragment-x2 frag)
427 (- (reduce #'+ (mapcar #'chunk-width (line-fragment-chunks frag)))))
429 (otherwise
430 (line-fragment-x1 frag))))
431 (ys nil)
432 (dy 0))
433 (labels ((walk-aux (chunks &aux text-seen-p);; -> cont
434 (do ()
435 ((null chunks)
436 (values nil nil text-seen-p))
437 (let ((chunk (pop chunks)))
438 (typecase chunk
439 (close-chunk
440 (return (values chunks chunk text-seen-p)))
441 (open-chunk
442 (let ((oc chunk)
443 (replaced-object-p (replaced-object-chunk-p (car chunks))))
444 (incf x (chunk-width chunk))
445 (when *debug-tags*
446 (let ((q (format nil "~A~A"
447 (chunk-debug-name chunk)
449 (format nil "(~D)"
450 (open-chunk-dy chunk))
452 (when (eql pass 1)
453 (clim:draw-text* clim-user::*pane* q x y))
454 (incf x (clim:text-size clim-user::*pane* q))))
455 (push dy ys)
456 (setf dy (open-chunk-dy chunk))
457 (push (bounding-chunk-style chunk) ss)
459 (let ((link (and (eql pass 1)
460 (closure-protocol:element-imap closure-protocol:*document-language*
461 *document*
462 (bounding-chunk-pt chunk))))
463 (x1 x))
464 (when (eql :img (element-gi (bounding-chunk-pt chunk)))
465 (setf *dyn-elm* (bounding-chunk-pt chunk)))
466 (let (p q res.text-seen-p)
467 (cond (link
468 (clim:with-output-as-presentation
469 (clim-user::*pane*
470 (url:unparse-url
471 (hyper-link-url (imap-area-link link)))
472 'clim-user::url
473 :record-type 'link-presentation)
474 (setf (values p q res.text-seen-p)
475 (walk chunks))))
477 (setf (values p q res.text-seen-p)
478 (walk chunks))))
479 (setf chunks p)
480 (setf text-seen-p (or text-seen-p res.text-seen-p))
481 (when q
482 (when *debug-tags*
483 (let ((q
484 (format nil "~A~A"
485 (chunk-debug-name q)
486 "")))
487 (when (eql pass 1)
488 (clim:draw-text* clim-user::*pane* q x y))
489 (incf x (clim:text-size clim-user::*pane* q))
492 (pop ss)
493 (when (eql pass 0)
494 (let ((x1 (- x1 (cooked-style-padding-left (bounding-chunk-style oc))))
495 (x (+ x (if q (cooked-style-padding-right (bounding-chunk-style q))
496 0))))
497 ;; See comment in VERTICALLY-ALIGN-LINE. Dimensions of
498 ;; replaced objects are different to dimensions of regular
499 ;; inline boxen.
500 (cond (replaced-object-p
501 (draw-box-decoration clim-user::*pane*
502 x1 (- (+ y dy) (open-chunk-height oc)
503 (cooked-style-padding-top (bounding-chunk-style oc))
504 (- (cooked-style-padding-top (bounding-chunk-style oc)))
505 (- (cooked-style-margin-top (bounding-chunk-style oc)))
506 (- (cooked-style-border-top-width (bounding-chunk-style oc))))
507 x (+ (+ y dy) (open-chunk-depth oc)
508 (cooked-style-padding-bottom (bounding-chunk-style oc))
509 (- (cooked-style-padding-bottom (bounding-chunk-style oc)))
510 (- (cooked-style-margin-bottom (bounding-chunk-style oc)))
511 (- (cooked-style-border-bottom-width (bounding-chunk-style oc))))
512 (bounding-chunk-style oc)
513 :left-halfp (not (bounding-chunk-halfp oc))
514 :right-halfp (not (bounding-chunk-halfp q))
517 (draw-box-decoration clim-user::*pane*
518 x1 (- (+ y dy) (open-chunk-height oc)
519 (cooked-style-padding-top (bounding-chunk-style oc)))
520 x (+ (+ y dy) (open-chunk-depth oc)
521 (cooked-style-padding-bottom (bounding-chunk-style oc)))
522 (bounding-chunk-style oc)
523 :left-halfp (not (bounding-chunk-halfp oc))
524 :right-halfp (not (bounding-chunk-halfp q))
525 )))))
526 (setf dy (pop ys))
527 ;; ### find out exact coordinates the text-deco should run over
528 ;; most probable: between the inner edges.
529 (when (eql pass 1)
530 (when res.text-seen-p
531 (draw-text-decoration x1 (+ y dy) x
532 (cooked-style-text-decoration (bounding-chunk-style chunk))
533 (cooked-style-color (bounding-chunk-style chunk)))))
534 (when q
535 (incf x (chunk-width q)))))))
537 (kern-chunk
538 (incf x (chunk-width chunk)))
540 (black-chunk
541 (setf text-seen-p t)
542 (when (and ss (car ss))
543 (when (eql pass 1)
544 (setf (clim:medium-ink clim-user::*medium*)
545 (css-color-ink (cooked-style-color (black-chunk-style chunk))))
546 (clim-draw-runes* clim-user::*pane*
547 x (+ dy y)
548 (black-chunk-data chunk)
549 0 (length (black-chunk-data chunk))
550 (make-text-style-from-cooked-style (car ss))))
551 (incf x (chunk-width chunk))))
553 (replaced-object-chunk
554 (let ((ro (replaced-object-chunk-object chunk)))
555 (when (eql pass 1)
556 (closure/clim-device::draw-ro*
557 clim-user::*pane*
558 ro x (+ dy y)))
559 (incf x (chunk-width chunk))) )))))
561 (walk (chunks)
562 (walk-aux chunks))
564 #+NIL
565 (progn
566 (fresh-line)
567 (dolist (c (line-fragment-chunks frag))
568 (when (typep c 'black-chunk)
569 (princ (map 'string #'code-char (black-chunk-data c))))))
571 (let ((x1 x))
572 (multiple-value-bind (rest-chunks some-chunk text-seen-p)
573 (walk (line-fragment-chunks frag))
574 (when text-seen-p
575 ;; kludge!
576 (when (text-indent-chunk-p (car (line-fragment-chunks frag)))
577 (incf x1 (chunk-width (car (line-fragment-chunks frag)))))
578 (when (eql pass 1)
579 (draw-text-decoration x1 y x
580 (cooked-style-text-decoration (line-fragment-block-style frag))
581 (cooked-style-color (line-fragment-block-style frag)))))))
582 ))))
584 (incf y depth) )
587 (defun cooked-style-effective-line-height (style)
588 (let ((line-height (cooked-style-line-height style)))
589 (if (and (consp line-height) (eq (car line-height) '*))
590 ;;### what is with rounding here?
591 (round (* (cdr line-height)
592 (cooked-style-font-size style)))
593 line-height)))
595 (defun text-height-and-depth (style)
596 (let* ((as (cooked-style-font-ascent style))
597 (ds (cooked-style-font-descent style))
598 (lh (cooked-style-effective-line-height style))
599 (hl-1 (floor (- lh (+ as ds)) 2))) ;DEVRND
600 (let* ((ds2 (max 0 (+ ds hl-1)))
601 (as2 (- lh ds2)))
602 (assert (= lh (+ as2 ds2)))
603 (values as2 ds2))))
605 (defun replaced-object-chunk-p (x)
606 (typep x 'replaced-object-chunk))
608 (defvar *tops* nil)
609 (defvar *btms* nil)
611 (defun vertically-align-line (fragments &optional no-line-height-mode)
612 ;; Note: The logic in here is a kind of wicked. A line box solely
613 ;; consisting out of replaced objects is assumed to have no ascent
614 ;; or descent on its own. This is indicated by the parameter
615 ;; 'no-line-height-mode' which when non-NIL tells this routine to
616 ;; ignore contributions of the line-height of the line box. A first
617 ;; run is attempted with no-line-height-mode turned off. After we
618 ;; finished the flags 'text-seen-p' and 'replaced-object-seen-p'
619 ;; tell us if we had seen the indicated kinds of entities. When
620 ;; appropriate we rerun with 'no-line-height-mode' turned on.
621 (let ((*tops* nil) (*btms* nil))
622 (let ((total-height 0)
623 (total-depth 0)
624 (text-seen-p nil)
625 (replaced-object-seen-p nil))
626 (dolist (frag fragments)
627 ;; this is a fragment, we would like to include its line-height into the
628 ;; calculation:
629 (multiple-value-bind (h d)
630 (cond ((replaced-object-chunk-p (car (line-fragment-chunks frag)))
631 (let ((ro (replaced-object-chunk-object (car (line-fragment-chunks frag)))))
632 (values (+ (nth-value 1 (ro/size ro)) (nth-value 2 (ro/size ro))) 0)))
633 (no-line-height-mode
634 (values 0 0))
636 (text-height-and-depth (line-fragment-block-style frag))))
637 (maxf total-height h)
638 (maxf total-depth d))
639 (let ((frag-chunk (make-instance 'open-chunk :style (line-fragment-block-style frag))))
640 (let ((chunk-stack (list frag-chunk))
641 dys (cur-dy 0))
642 (do ((q (line-fragment-chunks frag) (cdr q)))
643 ((null q))
644 (let ((chunk (car q)))
645 (etypecase chunk
646 (open-chunk
647 (multiple-value-bind (h d)
648 ;; Note that CSS makes a cruel distinction between inline replaced object
649 ;; and regular inline elements. The top and bottom of replaced object is at
650 ;; the outer edge, while the top and bottom of regular inline elements is
651 ;; at the inner edge. So we have to make a distinction here.
652 ;; Likewise we need to make a distinction in the line renderer.
653 (cond ((replaced-object-chunk-p (cadr q))
654 (setf replaced-object-seen-p t)
655 (let ((ro (replaced-object-chunk-object (cadr q)))
656 (s (bounding-chunk-style chunk)))
657 (values (+ (nth-value 1 (ro/size ro)) (nth-value 2 (ro/size ro))
658 (cooked-style-margin-top s)
659 (cooked-style-border-top-width s)
660 (cooked-style-padding-top s))
661 (+ (cooked-style-margin-bottom s)
662 (cooked-style-border-bottom-width s)
663 (cooked-style-padding-bottom s)))))
665 (no-line-height-mode
666 (values 0 0))
668 (text-height-and-depth (bounding-chunk-style chunk))))
670 (setf (values (open-chunk-height chunk) (open-chunk-depth chunk))
671 (values h d))
672 (let ((dy (- (resolve-valign chunk (car chunk-stack)))))
673 (push cur-dy dys)
674 (push chunk chunk-stack)
675 (incf cur-dy dy)
676 (unless (member (cooked-style-vertical-align (bounding-chunk-style chunk))
677 '(:top :bottom))
678 (maxf total-height (- h dy))
679 (maxf total-depth (+ d dy)))
680 (setf (open-chunk-dy chunk) cur-dy))))
681 (close-chunk
682 (setf cur-dy (pop dys))
683 (pop chunk-stack))
684 (replaced-object-chunk
685 ;; ###
687 (kern-chunk
688 nil)
689 (black-chunk
690 (setf text-seen-p t)
691 ;; nothing to do
692 nil)))))))
694 (cond ((and (not text-seen-p) replaced-object-seen-p (not no-line-height-mode))
695 (vertically-align-line fragments t))
697 ;; now after all care for top/bottom aligend stuff.
698 (dotimes (i 2)
699 (when *tops*
700 (dolist (k *tops*)
701 (setf (open-chunk-dy k) (- (open-chunk-height k) total-height))
702 (setf total-height (max total-height (- (open-chunk-height k) (open-chunk-dy k))))
703 (setf total-depth (max total-depth (+ (open-chunk-depth k) (open-chunk-dy k)))) ))
704 (when *btms*
705 (dolist (k *btms*)
706 (setf (open-chunk-dy k) (- total-depth (open-chunk-depth k)))
707 (setf total-height (max total-height (- (open-chunk-height k) (open-chunk-dy k))))
708 (setf total-depth (max total-depth (+ (open-chunk-depth k) (open-chunk-dy k)))) )))
710 (values total-height total-depth) )))))
712 (defun cooked-style-font-ascent (style)
713 (font-desc-ascent (text-style-font (make-text-style-from-cooked-style style))))
715 (defun cooked-style-font-descent (style)
716 (font-desc-descent (text-style-font (make-text-style-from-cooked-style style))))
718 (defun cooked-style-font-xheight (style)
719 (font-desc-x-height (text-style-font (make-text-style-from-cooked-style style))))
721 (defun resolve-valign (oc parent-oc)
722 (let* ((style (bounding-chunk-style oc))
723 (valign (cooked-style-vertical-align style)))
724 (cond ((realp valign)
725 valign)
727 ;; Percentage values are relative to the line height of the element.
728 ;; [Note: This should in theory be handled in css-properties.lisp, in practice
729 ;; line-height is special because it inherits differently].
731 ((and (consp valign) (eql (car valign) :%))
732 (* (/ (cdr valign) 100)
733 (cooked-style-effective-line-height style)))
736 (ecase valign
737 (:BASELINE
739 (:MIDDLE
740 ;; align the vertical midpoint of the element (typically an image) with
741 ;; the baseline plus half the x-height of the parent
742 (if (null parent-oc)
743 (progn
744 (warn "Cannot align middle without a parent element.")
746 (/ (+ (- (open-chunk-depth oc) (open-chunk-height oc))
747 (cooked-style-font-ascent (bounding-chunk-style parent-oc)))
748 2)))
749 (:IMG-MIDDLE
750 (floor (+ (- (open-chunk-depth oc) (open-chunk-height oc))) 2))
751 (:SUB
752 (/ (- (cooked-style-font-xheight style)) 2))
753 (:SUPER
754 (/ (+ (cooked-style-font-xheight style)) 1))
755 (:TEXT-TOP
756 ;; align the top of the element with the top of the parent element's font
757 (if (null parent-oc)
758 (progn
759 (warn "Cannot align 'text-top' without parent.")
761 (- (cooked-style-font-ascent (bounding-chunk-style parent-oc))
762 (open-chunk-height oc))))
763 (:TEXT-BOTTOM
764 ;; align the bottom of the element with the bottom of the parent element's font
765 (if (null parent-oc)
766 (progn
767 (warn "Cannot align 'text-bottom' without parent.")
769 (- (open-chunk-depth oc)
770 (cooked-style-font-descent (bounding-chunk-style parent-oc)))))
771 (:TOP
772 (push oc *tops*)
773 :top
775 (:BOTTOM
776 (push oc *btms*)
777 :bottom
778 0) )))))
780 (defun disc-chunk-p (x)
781 (typep x 'disc-chunk))
783 ;;; on floating boxen
785 ;;; Notes:
787 ;; CSS-2 9.5:
788 ;; | A floated box must have an explicit width (assigned via the
789 ;; | 'width' property, or its intrinsic width in the case of replaced
790 ;; | elements).
792 ;; | If there isn't enough horizontal room on the current line for the
793 ;; | float, it is shifted downward, line by line, until a line has
794 ;; ^^^^^^^^^^^^
795 ;; This is what we did.
796 ;; | room for it.
798 ;; | When a block box overlaps, the background and borders of the
799 ;; | block box are rendered behind the float and are only be visible
800 ;; | where the box is transparent. The content of the block box is
801 ;; | rendered in front of the float.
803 ;; note that the 'floating box' is essentially before all block
804 ;; boxen background but behind all 'text'.
806 ;;; Implementation
808 (defvar *floating-boxes*
810 "A list of all currently mounted floating boxes as their floating-chunk.")
812 (defun mount-floating-box (chunk x y)
813 "Mounts a floating chunk so that its outer top-left edge is at position x,y."
814 (let ((x1 x)
815 (x2 (+ x (floating-chunk-width chunk))))
816 (multiple-value-bind (vmp vmn yy yy0 bm)
817 (let ((*floating-boxes* nil)) ;### hmm
818 (format-block (floating-chunk-content chunk) x1 x2
819 #|ss:|# nil
820 #|before-markers:|# nil
821 #|vmargins:|# 0 0
822 #|y|# y))
823 (incf yy vmp) ;flush the vertical margin
824 (incf yy vmn) ;flush the vertical margin
825 (setf (floating-chunk-x1 chunk) x1
826 (floating-chunk-x2 chunk) x2
827 (floating-chunk-y1 chunk) y
828 (floating-chunk-y2 chunk) yy)
829 (push chunk *floating-boxes*) )))
831 (defun find-margins (x1 x2 y)
832 "Find effective margin at vertical position /y/ considering all
833 mounted floating boxen."
834 (dolist (fb *floating-boxes*)
835 (multiple-value-bind (f.x1 f.y1 f.x2 f.y2 side)
836 (values (floating-chunk-x1 fb) (floating-chunk-y1 fb)
837 (floating-chunk-x2 fb) (floating-chunk-y2 fb)
838 (cooked-style-float (floating-chunk-style fb)))
839 (when (and (<= f.y1 y) (< y f.y2)) ;## might rethink this cond.
840 (case side
841 (:left
842 (setf x1 (max x1 f.x2)))
843 (:right
844 (setf x2 (min x2 f.x1)))))))
845 ;; it shouldn't happend that x1 < x2.
846 (values x1 x2))
848 ;;; vertical margin:
850 ;; Vertical margin has only to be flushed when we really see some line with
851 ;; something on it. The only complication is that we should assume vertical
852 ;; margin to be flushed for finding out the x1, x2 coordinates.
855 (defun format-para (items pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style before-markers)
856 ;; ### we want another special rule to force floating boxen which occur on otherwise empty lines.
857 ;; (i am still not sure if this is necessarily a good idea).
858 (let (style ;###
859 (clw 0)
860 (cww 0)
861 (cur-line nil)
862 (cur-word nil)
863 (word-fboxen nil) ;(reversed) list of floating boxen
864 ; attached to the current word.
866 (line-fboxen nil) ;(reversed) list of floating boxen
867 ; attached to the current line.
868 (ox1 x1)
869 (ox2 x2))
871 (when *hyphenate-p*
872 (setf items (hyphenate-items items (- x2 x1))))
874 (labels ((new-margins ()
875 "finds the new margins, this takes the vertical margin into
876 account without flushing it."
877 (setf (values x1 x2)
878 (find-margins ox1 ox2
879 (+ yy pos-vertical-margin neg-vertical-margin))))
881 (flush-margin ()
882 "actually flushes the vertical margin."
883 (incf yy pos-vertical-margin)
884 (incf yy neg-vertical-margin)
885 (setf pos-vertical-margin 0)
886 (setf neg-vertical-margin 0))
888 (flush-line ()
889 "called with the current line is about to be emitted. Has
890 some extra 'safety' test to see if there is actually
891 something on it."
892 (cond ((or
893 (some #'(lambda (chunk)
894 (not (or
895 ;; half chunks don't count
896 (and (bounding-chunk-p chunk)
897 (bounding-chunk-halfp chunk))
898 ;; also first-line start end chunks don't count
899 (and (bounding-chunk-p chunk)
900 (typep (bounding-chunk-pt chunk) 'first-line-pseudo-element)))))
901 cur-line)
902 before-markers)
903 ;; okay there is something, build the line-fragments
904 (let ((fragments
905 (list*
906 (make-instance 'line-fragment :x1 x1 :x2 x2
907 :block-style block-style
908 :chunks (reverse cur-line))
909 ;; possible markers
910 (mapcar (lambda (marker)
911 (make-instance 'line-fragment
912 :x1 (marker-box-x1 marker)
913 :x2 (marker-box-x2 marker)
914 :block-style block-style ;### correct?
915 :chunks
916 ;; ### hmm we assume a hell lot about this marker box.
917 (para-box-items (first (marker-box-content marker)))))
918 before-markers))))
919 (setf before-markers nil) ;before markers are gone now
920 ;; render it
921 ;; but first flush the margin
922 (flush-margin)
923 (setf yy (render-line yy fragments))))
925 ;; otherwise just forget the current line
928 (setf cur-line nil
929 clw 0)
930 (new-margins)
931 ;; we need to decide what to do with the line-fboxen ...
932 (consider-fboxen (prog1 (reverse line-fboxen)
933 (setf line-fboxen nil)))
934 (new-margins))
936 (consider-fboxen (fboxen)
937 "Consider all the floating boxen in fboxen in order and
938 mount any that fit. Those that do not fit land up in line-fboxen."
939 (let ((flag nil))
940 (dolist (k fboxen)
941 (cond ((and (not flag)
942 (<= (+ clw (floating-chunk-width k)) (- x2 x1))
943 (if (member (cooked-style-clear (floating-chunk-style k)) '(:left :both))
944 (>= (+ yy pos-vertical-margin neg-vertical-margin)
945 (clear-y-coordinate :left))
947 (if (member (cooked-style-clear (floating-chunk-style k)) '(:right :both))
948 (>= (+ yy pos-vertical-margin neg-vertical-margin)
949 (clear-y-coordinate :right))
951 ;; still fits, mount it
952 (mount-fbox k))
954 ;; does not fit
955 (push k line-fboxen)
956 (setf flag t)))) ))
958 (mount-fbox (chunk &optional (y (+ yy pos-vertical-margin neg-vertical-margin)))
959 (mount-floating-box chunk
960 (case (cooked-style-float (floating-chunk-style chunk))
961 (:left x1)
962 (:right (- x2 (floating-chunk-width chunk))))
964 (new-margins))
966 (flush-word ()
967 (tagbody
968 re-consider
969 (when (> cww (- x2 x1))
970 ;; we have a change though
971 (flush-margin)
972 (unless (= (- x2 x1) (- ox2 ox1))
973 ;; some floating boxen must be in the way, move
974 ;; past them ...
976 ;; ### Question: although going in natural-line-height
977 ;; increments is fine it might be more correct to place
978 ;; floating boxen which take their priority at the highest
979 ;; possible position.
981 (let ((clear-y (reduce #'max (mapcar #'floating-chunk-y2 *floating-boxes*))))
982 (let ((natural-line-height (cooked-style-effective-line-height block-style)))
983 (do ((y (+ yy)
984 (+ y natural-line-height)))
985 ((>= y clear-y)
986 (setf yy y))
987 (multiple-value-bind (x1 x2) (find-margins ox1 ox2 y)
988 (when (<= cww (- x2 x1))
989 (setf yy y)
990 (return))))))
991 (new-margins)
992 ;; now: floating boxen have priority
993 (consider-fboxen (prog1 (reverse line-fboxen)
994 (setf line-fboxen nil)))
995 (new-margins)
996 (go re-consider))))
998 (when (> cww (- x2 x1))
999 ;; situation persists: give a warning.
1000 (when *debug-tables*
1001 (warn "*** Overfull line box.")))
1003 (dolist (k (reverse cur-word)) (push k cur-line))
1004 (setf cur-word nil)
1005 (incf clw cww)
1006 (setf cww 0)
1007 (consider-fboxen (prog1
1008 (reverse word-fboxen)
1009 (setf word-fboxen nil))) )
1012 (process (xs)
1013 (let ((x (car xs)))
1014 (typecase x
1015 (floating-chunk
1016 (compute-floating-chunk-width x (- ox2 ox1))
1017 ;; this is a special rule: when the word is otherwise
1018 ;; empty we could as well try to mount the floating box
1019 ;; now.
1020 (cond ((= cww 0) ;; ### (null cur-word)
1021 (push x word-fboxen)
1022 (flush-word))
1024 (push x word-fboxen))))
1025 (open-chunk
1026 (push (bounding-chunk-style x) ss)
1027 (incf cww (chunk-width x))
1028 (push x cur-word))
1029 (close-chunk
1030 (pop ss)
1031 (incf cww (chunk-width x))
1032 (push x cur-word))
1033 (disc-chunk
1034 (cond ((disc-chunk-forcep x)
1035 (flush-word)
1036 (show (disc-chunk-before x))
1037 (flush-word)
1038 (flush-line)
1039 (show (disc-chunk-after x)))
1041 (flush-word)
1042 (let ((p (disc-chunk-here x))
1043 (ww 0))
1044 (block zulu
1045 (dolist (k p)
1046 (cond ((typep k 'disc-chunk)
1047 ;; before is missing.
1048 ;; actually this is a stranger situation ...
1049 (return-from zulu))
1050 ((typep k 'floating-chunk)
1051 nil) ;these don't count.
1053 (incf ww (chunk-width k))) ))
1054 ;; disc exhausted continue with list
1055 (let ((ss ss))
1056 (dolist (k (cdr xs))
1057 (cond ((typep k 'disc-chunk)
1058 ;; before is missing.
1059 ;; actually this is a stranger situation ...
1060 (return-from zulu))
1061 ((typep k 'floating-chunk)
1062 nil)
1063 ((typep k 'open-chunk)
1064 (incf ww (chunk-width k))
1065 (push (bounding-chunk-style k) ss))
1066 ((typep k 'close-chunk)
1067 (incf ww (chunk-width k))
1068 (pop ss))
1069 ((typep k 'replaced-object-chunk)
1070 ;; hhmm..
1071 (multiple-value-bind (width height)
1072 (replaced-object-dimensions (replaced-object-chunk-object k)
1073 (cooked-style-width (car ss))
1074 (cooked-style-height (car ss)))
1075 (ro/resize (replaced-object-chunk-object k)
1076 width height)
1077 (incf ww width)))
1079 (incf ww (chunk-width k)))))))
1080 (cond ((<= (+ clw ww) (- x2 x1))
1081 (show p))
1083 (show (disc-chunk-before x))
1084 (flush-word)
1085 (flush-line)
1086 (show (disc-chunk-after x))))))))
1087 (replaced-object-chunk
1088 ;; this is the time to resize
1089 (multiple-value-bind (width height)
1090 (replaced-object-dimensions (replaced-object-chunk-object x)
1091 (cooked-style-width (car ss)) (cooked-style-height (car ss)))
1092 (ro/resize (replaced-object-chunk-object x)
1093 width height)
1094 (incf cww width))
1095 (push x cur-word))
1096 (kern-chunk
1097 (incf cww (chunk-width x))
1098 (push x cur-word))
1099 (black-chunk
1100 (incf cww (chunk-width x))
1101 (push x cur-word)) ))
1104 (show (seq)
1105 (do ((q seq (cdr q)))
1106 ((null q))
1107 (setf q (process q)) )) )
1109 ;; ### should text-indention be within the first-line pseudo element or
1110 ;; outside?
1111 (let ((text-indent (cooked-style-text-indent block-style)))
1112 ;; now: this is art! text-indent
1113 (unless (zerop text-indent)
1114 (push (make-text-indent-chunk text-indent)
1115 items)))
1117 (new-margins)
1119 (show items)
1120 (flush-word)
1121 (flush-line)
1123 ;; after all there might be floating boxen still flying around. Mount
1124 ;; them where they fit but do not affect the yy coordinate.
1125 (when line-fboxen
1126 (let ((oyy yy)
1127 (oneg-vertical-margin neg-vertical-margin)
1128 (opos-vertical-margin pos-vertical-margin))
1130 (flush-margin)
1131 (new-margins)
1133 ;; ### hmm can it be that the 'not any higher' is for each side?
1134 ;; look that up!
1136 (dolist (k (reverse line-fboxen)) ;one after another
1137 (block yseek
1138 (dolist (y (cons yy (sort (mapcar #'floating-chunk-y2 *floating-boxes*)
1139 #'<)))
1140 (setf yy y)
1141 (new-margins)
1142 (when (and (<= (floating-chunk-width k) (- x2 x1))
1143 (if (member (cooked-style-clear (floating-chunk-style k)) '(:left :both))
1144 (>= (+ yy pos-vertical-margin neg-vertical-margin)
1145 (clear-y-coordinate :left))
1147 (if (member (cooked-style-clear (floating-chunk-style k)) '(:right :both))
1148 (>= (+ yy pos-vertical-margin neg-vertical-margin)
1149 (clear-y-coordinate :right))
1151 (mount-fbox k yy)
1152 (return-from yseek)))
1153 ;; when we land here our y coordinates are exhausted, mount the
1154 ;; floating box never the less.
1155 (mount-fbox k yy) ))
1157 (setf yy oyy
1158 neg-vertical-margin oneg-vertical-margin
1159 pos-vertical-margin opos-vertical-margin) )))
1161 ;; ### now after all we _need_ to mount all floating boxen which still
1162 ;; exist.
1164 (values pos-vertical-margin neg-vertical-margin ox1 ox2 yy ss block-style) ))
1166 (defun render2 (device document pt selected-style)
1167 (declare (ignorable device document pt selected-style))
1169 (setf *floating-boxes* nil)
1170 (setf *device* device
1171 *document* document)
1172 (let* ((css::*device* device)
1173 (*style-sheet* (document-style-sheet document))
1174 (css::*style-sheet* *style-sheet*)
1175 (rc (make-rc :device device :y 0 :x0 0 :x1 *canvas-width*
1176 :vertical-margins nil
1177 :vertical-margin-callbacks nil
1178 :first-line-tasks nil
1179 :left-floating-boxen nil
1180 :right-floating-boxen nil
1181 :document document ))
1182 (*rcontext* rc))
1183 (setf *zzz* (po (flatten-pt pt) :dont-care))
1184 (setf *document-height*
1185 (nth-value 2
1186 (format-block *zzz* 0 *canvas-width* nil nil 0 0 0) )) ))
1188 (defun reflow ()
1189 (setf *document-height*
1190 (nth-value 2
1191 (format-block *zzz* 0 *canvas-width* nil nil 0 0 0))))
1193 (defvar *zzz* nil)
1194 (defvar *dyn-elm* nil)
1196 (defun format-block (item x1 x2 ss before-markers #||# pos-vertical-margin neg-vertical-margin yy)
1197 (let (res)
1198 (setf (block-box-output-record item)
1199 (clim:with-new-output-record (clim-user::*pane*) #+nil foo
1200 (setf res
1201 (multiple-value-list
1202 (case (cooked-style-display (block-box-style item))
1203 (:table
1204 (incf yy pos-vertical-margin)
1205 (incf yy neg-vertical-margin)
1206 (setf yy
1207 (format-table item x1 x2 yy (block-box-style item)))
1208 (values 0 0 yy))
1209 (otherwise
1210 (format-block-aux item
1211 x1 x2
1213 before-markers
1214 pos-vertical-margin
1215 neg-vertical-margin
1216 yy) ))))))
1217 (values-list res)))
1219 (defun format-block-aux (block-box x1 x2 ss before-markers
1220 #|in-out|# pos-vertical-margin neg-vertical-margin yy)
1222 ;; ###
1223 ;; now vertical should only be flushed in the following circumstances:
1224 ;; . a line is actually mounted.
1225 ;; . padding is 'mounted'
1226 ;; . a border is 'mounted'
1229 (let* ((items (block-box-content block-box))
1230 (block-style (block-box-style block-box))
1231 (s block-style)
1232 (tm (cooked-style-margin-top s))
1233 (bm (cooked-style-margin-bottom s))
1234 (tp (cooked-style-padding-top s))
1235 (bp (cooked-style-padding-bottom s))
1236 (yy0 nil) ;the inner top padding edge
1237 ; NIL initially to indicate that we do not know it for now.
1238 (bg-record
1239 (clim:with-new-output-record (clim-user::*pane*)
1242 ;; remember the output record of the decoration
1243 (setf (block-box-decoration-output-record block-box)
1244 bg-record)
1246 (labels
1247 ((flush-margin ()
1248 "Flushes the vertical margin"
1249 (incf yy pos-vertical-margin)
1250 (incf yy neg-vertical-margin)
1251 (setf pos-vertical-margin 0)
1252 (setf neg-vertical-margin 0)))
1254 (multiple-value-bind (ml bl pl wd pr br mr)
1255 (values (if (eql (cooked-style-display s) :table-cell)
1256 0 ;table cells don't have margins ### better way to do that?
1257 (cooked-style-margin-left s))
1258 (if (eql (cooked-style-display s) :table-cell)
1259 0 ;table cells don't have border (they are drawn by the table renderer)
1260 (cooked-style-border-left-width s))
1261 (cooked-style-padding-left s)
1262 (cooked-style-width s)
1263 (cooked-style-padding-right s)
1264 (if (eql (cooked-style-display s) :table-cell)
1265 0 ;table cells don't have border (they are drawn by the table renderer)
1266 (cooked-style-border-right-width s))
1267 (if (eql (cooked-style-display s) :table-cell)
1268 0 ;table cells don't have margins ### better way to do that?
1269 (cooked-style-margin-right s)))
1270 (push s ss)
1272 ;; top margin
1273 (when (realp tm)
1274 (if (> tm 0)
1275 (maxf pos-vertical-margin tm)
1276 (minf neg-vertical-margin tm)))
1278 ;;; clear
1280 ;; Clear should work by increasing the top-margin of the box so that
1281 ;; the top border edge is below any offending floating.
1283 ;; Caution: this somehow magically also applies to floating boxen the
1284 ;; meaning of which in that context is unclear.
1286 ;; this formulation is clumspy with respect to collapsing margins, since
1287 ;; on a block box which has no border or padding the y-coordinate of the
1288 ;; border top edge is not known in advance.
1290 ;; we do the easy route and assert:
1292 ;; clear-y = y-coordinate that is clear
1293 ;; yy + vertical-margin
1294 ;; = yy + max(pos-vertical-margin, margin-top) + neg-vertical-margin
1295 ;; = border-top-edge
1297 ;; => max(pos-vertical-margin, margin-top)
1298 ;; = clear-y - neg-vertical-margin - yy
1300 ;; margin-top >= clear-y - neg-vertical-margin - yy
1301 ;; => max(pos-vertical-margin, margin-top) >= clear-y - neg-vertical-margin - yy
1302 ;; => max(pos-vertical-margin, margin-top) + neg-vertical-margin + yy = border-top-edge >= clear-y
1305 (let ((clear (cooked-style-clear s)))
1306 (unless (eql :clear :none)
1307 (let ((clear-y 0))
1308 (when (member clear '(:left :both))
1309 (setf clear-y (max clear-y (clear-y-coordinate :left))))
1310 (when (member clear '(:right :both))
1311 (setf clear-y (max clear-y (clear-y-coordinate :right))))
1312 (setf pos-vertical-margin
1313 (max pos-vertical-margin
1314 (- clear-y neg-vertical-margin yy))))))
1316 ;; top border
1317 (unless (zerop (cooked-style-border-top-width s))
1318 (flush-margin)
1319 (unless yy0
1320 (setf yy0 yy))
1321 (incf yy (cooked-style-border-top-width s)))
1323 ;; top padding
1324 (when (/= tp 0)
1325 (flush-margin)
1326 (unless yy0
1327 (setf yy0 yy))
1328 (incf yy tp))
1330 (incf x1 (+ ml bl pl))
1331 (decf x2 (+ mr br pr))
1333 ;;(assert (= x2 (+ x1 wd)))
1335 (setf x2 (+ x1 wd))
1337 (dolist (item items)
1338 (etypecase item
1339 (para-box
1340 (let ((op pos-vertical-margin) (on neg-vertical-margin) (oy yy))
1342 (let ((.*document* *document*))
1343 (setf (para-box-genesis item)
1344 (multiple-value-bind (pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style)
1345 (values pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style)
1346 (lambda ()
1347 (let ((*document* .*document*))
1348 (format-para (para-box-items item)
1349 pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style
1350 before-markers))))))
1352 (setf (para-box-output-record item)
1353 (clim:with-new-output-record (clim-user::*pane*)
1354 (setf (values pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style)
1355 (funcall (para-box-genesis item)))))
1357 ;; EVIL: from the fact that pos and neg margin are now zero, we can
1358 ;; deduce that the margin must have been flushed.
1359 (when (and (null yy0)
1360 (= pos-vertical-margin neg-vertical-margin 0))
1361 (setf yy0 (+ oy op on)))
1364 (setf before-markers nil))
1366 (block-box
1367 ;; Here the block has to tell us, where it will flush the margin. Now
1368 ;; every block flushes its margin (eventually) but the exact amount
1369 ;; may not be known.
1370 (let (yy00)
1371 (setf (values pos-vertical-margin neg-vertical-margin yy yy00)
1372 (format-block item x1 x2 ss before-markers
1373 pos-vertical-margin neg-vertical-margin yy))
1374 (unless yy0
1375 (setf yy0 yy00)))
1376 (setf before-markers nil) )
1378 (marker-box
1379 ;; ### care for right markers
1380 ;; ### is this an after marker?
1381 (let (mx1 mx2)
1382 ;; find out the margins we want to typeset into
1383 (setf mx1 (- x1 pl bl ml)
1384 mx2 (- x1 pl bl))
1385 ;; find the width of the marker box and its marker offset for the
1386 ;; definite marker box margins.
1387 ;; ### what should padding, margin et al to do marker boxen?
1389 ;; ### ouch: the containing block for this marker perhaps is not
1390 ;; the containing block of the paragraph.
1392 ;; ### now after all is the containing block of an element
1393 ;; constant?
1395 (let ((min (minmax-para (para-box-items(car(marker-box-content item))) block-style))
1396 (offset (cooked-style-marker-offset (marker-box-style item))))
1397 #+NIL
1398 (dprint "Min width of ~S is ~S. offset = ~S"
1399 (marker-box-content item) min
1400 offset)
1401 (setf offset 6) ;###
1402 (setf mx1 (- mx2 min offset)))
1403 (setf (marker-box-x1 item) mx1
1404 (marker-box-x2 item) mx2)
1406 (push item before-markers)
1407 ))))
1409 ;; when there are any markers left, spill them now
1410 (when before-markers
1411 ;; ### evil: markers by (our) definition are always non-empty, so we can
1412 ;; flush the margin now.
1413 ;; ### now format-para is assumed to flush the margin if needed.
1414 (flush-margin)
1415 (unless yy0
1416 (setf yy0 yy))
1417 (values pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style)
1418 (format-para nil pos-vertical-margin neg-vertical-margin x1 x2 yy ss block-style
1419 before-markers))
1421 (unless yy0
1422 ;; ### now this is questionable, should we really force a margin flush?
1423 (flush-margin)
1424 (unless yy0
1425 (setf yy0 yy)))
1427 ;;; height
1429 ;; It might happen that the user specified a height (which refers to
1430 ;; the content height of a box). The inner top edge is given by yy0.
1431 ;; Now specifying a height _forces_ the height to the given value
1432 ;; regardless of the fact that we might need more space. So we take a
1433 ;; brute force route and simply assert the appropriate y cursor
1434 ;; position.
1436 ;; oops yy0 is the border-top-edge
1438 (let ((height (cooked-style-height s)))
1439 ;; now it seems that css1 includes padding here
1440 (unless (eql :auto height)
1441 (flush-margin) ;### should we actually do that?
1442 (setf yy (+ (+ yy0
1443 (cooked-style-border-top-width s)
1444 (cooked-style-padding-top s))
1445 height))))
1447 ;; bottom padding
1448 (when (/= bp 0)
1449 (flush-margin))
1450 (incf yy bp)
1452 ;; bottom border
1453 (unless (zerop (cooked-style-border-bottom-width s))
1454 (flush-margin)
1455 (incf yy (cooked-style-border-bottom-width s)))
1457 ;; bottom margin
1458 (when (realp bm)
1459 (if (> bm 0)
1460 (maxf pos-vertical-margin bm)
1461 (minf neg-vertical-margin bm)))
1464 (clim:with-output-recording-options (clim-user::*pane* :record t :draw nil)
1465 (let ((new-record
1466 (clim:with-new-output-record (clim-user::*pane*)
1468 (multiple-value-bind (x1 y1 x2 y2)
1469 (values (- x1 pl) (+ yy0
1470 (cooked-style-border-top-width s)
1472 (+ x2 pr) (- yy
1473 (cooked-style-border-bottom-width s)
1475 (draw-box-decoration clim-user::*pane* x1 y1 x2 y2 block-style)
1476 (incf y1 (cooked-style-padding-top s))
1477 (decf y2 (cooked-style-padding-bottom s))
1478 (when (realp (cooked-style-height s))
1479 (unless (= (cooked-style-height s)
1480 (- y2 y1))
1481 (error "Fubar")))
1482 #+NIL
1483 (unless (or (= x1 x2) (= y1 y2))
1484 (clim:draw-rectangle* clim-user::*pane* x1 y1 x2 y2
1485 :ink clim:+red+
1486 :filled nil))
1490 (clim:delete-output-record new-record (clim:output-record-parent new-record))
1491 (clim:add-output-record new-record bg-record)))
1493 (values pos-vertical-margin neg-vertical-margin yy yy0)))))
1498 (defmethod print-object ((object para-box) stream)
1499 (format stream "#<PARA-BOX ~S>" (para-box-items object)))
1501 (defmethod print-object ((object block-box) stream)
1502 (format stream "#<BLOCK-BOX ~S ~S>" (element-gi (block-box-element object)) (block-box-content object)))
1504 (defclass* marker-box ()
1505 ;; A marker box can occur as content of a block-box.
1507 ;; The formatting code is responsible for passing this marker box the first
1508 ;; paragraph that is applicable.
1509 x1 x2
1510 parent ;the block box this marker-box emerged from.
1511 style ;the style of the pseudo-element
1512 side ;either :left or :right (the side it has to be rendered to)
1513 content) ;the marker box content (a list of chunks)
1515 (defun block-box-p (object)
1516 (typep object 'block-box))
1518 (defun make-block-box (&key content style element)
1519 (unless (eql element (slot-value style 'css::%element))
1520 (warn "MAKE-BLOCK-BOX: style does match element (~S vs ~S)."
1521 (slot-value style 'css::%element) element))
1522 (make-instance 'block-box
1523 :element element
1524 :content content
1525 :style style))
1527 (defun marker-box-p (object)
1528 (typep object 'marker-box))
1530 (defun make-marker-box (&rest args &key x1 x2 style side content)
1531 (declare (ignore x1 x2 style side content))
1532 (apply #'make-instance 'marker-box args))
1534 (defun make-para-box (&key items)
1535 (make-instance 'para-box :items items))
1537 (defun para-box-p (object)
1538 (typep object 'para-box))
1540 (defun clear-y-coordinate (side)
1541 "Find smallest y coordinate clear of floating boxen on side 'side'."
1542 (let ((clear-y 0))
1543 (dolist (k *floating-boxes*)
1544 (when (eql side (cooked-style-float (floating-chunk-style k)))
1545 (setf clear-y (max clear-y (floating-chunk-y2 k)))))
1546 clear-y))
1548 (defmethod replaced-object-dimensions ((object t) given-width given-height)
1549 "This method is reponsible to return a replaced object actual dimensions
1550 under the situation that the user specified the width and height as
1551 'given-width' and 'given-height'. Both dimensions can either a real number
1552 or :auto meaning that the dimension was not explicitly given.
1554 It is the responsibility of the individual replaced object to decide if it
1555 wants to scale the other dimension if only one is given or if it wants to
1556 retain its intrinsic value.
1558 This method returns three values: width, ascent, descent. (Yes, replaced
1559 objects have something like a baseline contrary to the believe of the W3C)."
1560 (cond ((and (realp given-width) (realp given-height))
1561 ;; both are given
1562 (values given-width given-height 0))
1563 ((and (eql given-width :auto) (eql given-height :auto))
1564 ;; both are auto
1565 (ro/intrinsic-size object))
1567 ((eql given-width :auto)
1568 ;; only width is auto, scale height as appropriate
1569 (values
1570 (round ;xxx
1571 (if (zerop (+ (nth-value 1 (ro/intrinsic-size object))
1572 (nth-value 2 (ro/intrinsic-size object))))
1574 (* given-height (/ (nth-value 0 (ro/intrinsic-size object))
1575 (+ (nth-value 1 (ro/intrinsic-size object))
1576 (nth-value 2 (ro/intrinsic-size object)))))))
1577 given-height
1580 ((eql given-height :auto)
1581 (values
1582 (round given-width)
1583 (round (if (zerop (nth-value 0 (ro/intrinsic-size object)))
1585 (* given-width (/ (+ (nth-value 1 (ro/intrinsic-size object))
1586 (nth-value 2 (ro/intrinsic-size object)))
1587 (nth-value 0 (ro/intrinsic-size object))))))
1591 (error "Bogus arguments."))))
1594 (defun resolve-widthen (style cbwidth
1595 &optional (margin-left (cooked-style-margin-left style))
1596 (border-left-width (cooked-style-border-left-width style))
1597 (padding-left (cooked-style-padding-left style))
1598 (width (cooked-style-width style))
1599 (padding-right (cooked-style-padding-right style))
1600 (border-right-width (cooked-style-border-right-width style))
1601 (margin-right (cooked-style-margin-right style)) )
1602 ;; -> margin-left padding-left width padding-right margin-right
1603 (let ()
1605 ;; resolve possible percentage values
1607 (setf margin-left (maybe-resolve-percentage margin-left cbwidth))
1608 (setf padding-left (maybe-resolve-percentage padding-left cbwidth))
1609 (setf width (maybe-resolve-percentage width cbwidth))
1610 (setf padding-right (maybe-resolve-percentage padding-right cbwidth))
1611 (setf margin-right (maybe-resolve-percentage margin-right cbwidth))
1613 ;; deduct padding and border
1615 (decf cbwidth (+ padding-left padding-right border-left-width border-right-width))
1618 ;; now only margin and width are left
1620 (let ((ml margin-left) (mr margin-right) (wd width) (width-left cbwidth))
1622 ;; Alle Faelle betrachten
1623 (cond
1624 ;; CSS1 says: If none of the properties are 'auto', the value
1625 ;; of 'margin-right' will be assigned 'auto'.
1626 ((and (neq ml :auto) (neq wd :auto) (neq mr :auto)) ; - - -
1627 (setq mr (- width-left ml wd)))
1629 ;; CSS1 says: If exactly one of 'margin-left', 'width' or 'margin-right'
1630 ;; is 'auto', the UA will assign that property a value that will make the
1631 ;; sum of the seven equal to the parent's width.
1633 ((and (neq ml :auto) (neq wd :auto) ( eq mr :auto)) ; - - A
1634 (setq mr (- width-left ml wd)))
1636 ((and (neq ml :auto) ( eq wd :auto) (neq mr :auto)) ; - A -
1637 (setq wd (- width-left ml mr)))
1639 ((and ( eq ml :auto) (neq wd :auto) (neq mr :auto)) ; A - -
1640 (setq ml (- width-left wd mr)))
1642 ;; CSS1: If more than one of the three is 'auto', and one of them is
1643 ;; 'width', than the others ('margin-left' and/or 'margin-right') will be
1644 ;; set to zero and 'width' will get the value needed to make the sum of
1645 ;; the seven equal to the parent's width.
1647 ((and (neq ml :auto) ( eq wd :auto) ( eq mr :auto)) ; - A A
1648 (setq mr 0)
1649 (setq wd (- width-left ml mr)))
1651 ((and ( eq ml :auto) ( eq wd :auto) (neq mr :auto)) ; A A -
1652 (setq ml 0)
1653 (setq wd (- width-left ml mr)))
1655 ((and ( eq ml :auto) ( eq wd :auto) ( eq mr :auto)) ; A A A
1656 (setq ml 0 mr 0)
1657 (setq wd (- width-left ml mr)))
1659 ;; Otherwise, if both 'margin-left' and 'margin-right' are 'auto', they will
1660 ;; be set to equal values. This will center the element inside its parent.
1662 ((and ( eq ml :auto) (neq wd :auto) ( eq mr :auto)) ; A - A
1663 (setq ml (ceiling (- width-left wd) 2)) ;DEVRND
1664 (setq mr (floor (- width-left wd) 2)) ) ) ;DEVRND
1666 (values ml border-left-width padding-left wd padding-right border-right-width mr) )))
1668 (defun flatten-pt (pt &optional s)
1669 (cond ((text-element-p pt)
1670 (list (list :data (element-text pt) pt)))
1672 (append (list (list :open pt))
1673 (mapcan #'(lambda (x) (flatten-pt x s)) (element-children pt))
1674 (list (list :close pt))))))
1676 (defun my-setup-style (pt s containing-block-style)
1677 (assert (not (eq pt
1678 (and containing-block-style
1679 (slot-value containing-block-style 'css::%element)))))
1680 (css::setup-style-3
1681 *device* *document* *style-sheet* pt s containing-block-style))
1685 (let ((memo (make-hash-table :test #'eq)))
1686 (defun make-text-style-from-cooked-style (cooked-style)
1687 (or (gethash cooked-style memo)
1688 (setf (gethash cooked-style memo)
1689 (make-text-style *device*
1690 :font-family (cooked-style-font-family cooked-style)
1691 :font-weight (cooked-style-font-weight cooked-style)
1692 :font-size (cooked-style-font-size cooked-style)
1693 :font-style (cooked-style-font-style cooked-style)
1694 :font-variant (cooked-style-font-variant cooked-style)
1695 :letter-spacing (cooked-style-letter-spacing cooked-style)
1696 :word-spacing (cooked-style-word-spacing cooked-style))))))
1700 ;; | marker
1701 ;; |
1702 ;; | This value declares generated content before or after a box to be a
1703 ;; | marker. This value should only be used with :before and :after
1704 ;; | pseudo-elements attached to block-level elements. In other cases,
1705 ;; | this value is interpreted as 'inline'. Please consult the section
1706 ;; | on markers for more information.
1709 ;;;; CSS2 Tables Model
1711 ;; CSS2 always assumes:
1713 ;; <table> ::= <rowgroup>*
1714 ;; <rowgroup> ::= <row>*
1715 ;; <row> ::= <cell>*
1716 ;; <cell> ::=
1718 ;; missing elements have to be infered.
1719 ;; colspan and rowspan is not defined by CSS2.
1722 ;;; rowspan/colspan
1724 ;; rowspan and colspan for now is simply resolved by assigning row and
1725 ;; column indicies to cells.
1727 ;; "2. If the parent P of a 'table-cell' element T is not a
1728 ;; 'table-row', an object corresponding to a 'table-row' will be
1729 ;; generated between P and T. This object will span all consecutive
1730 ;; 'table-cell' siblings (in the document tree) of T."
1732 ;; White space?
1733 ;; this can be coded as:
1735 ;; The hard part about these rules is the requirement that consecutive
1736 ;; elements will be gathered into one table object. This implies that
1737 ;; this inference has to happen pretty early. So I guess we are
1738 ;; probably best of by doing this in the "popo" phase.
1741 ;; 17.5 Visual layout of table contents
1743 ;; | Cells may span several rows or columns. Each cell is thus a
1744 ;; | rectangular box, one or more grid cells wide and high. The top
1745 ;; | row of this rectangle is in the row specified by the cell's
1746 ;; | parent. The rectangle must be as far to the left as possible, but
1747 ;; | it may not overlap with any other cell box, and must be to the
1748 ;; | right of all cells in the same row that are earlier in the source
1749 ;; | document.
1751 ;; Hmm, is that generally true for HTML also?
1753 ;;; Collecting the table
1755 ;; Since as said in the spec the table model is row-centric, we first
1756 ;; collect the rowgroups and rows to the table. After that is done we
1757 ;; resolve rowspan and colspan by assigning a column and row index to
1758 ;; each cell. (OTH the row index should always be the row itself and
1759 ;; thus we'll need just a column index).
1761 ;; Now we now the number of columns and can actually start to gather
1762 ;; the columns and column groups. If these elements are not present in
1763 ;; the document tree we just generate anonymous elements.
1765 ;; Next step would be to derive the columns minimum and maxium
1766 ;; dimensions and assigning their actual size.
1768 ;; Actually rendering the table then is a bit awkward since we need to
1769 ;; align the table cells vertically also, which generally can only be
1770 ;; done after rendering said cell. Since we don't really support
1771 ;; incremental rendering we can as well rendering them with drawing
1772 ;; turned off and move the contents later on. This also implies that
1773 ;; we cannot simply use the block content renderer as it is for
1774 ;; rendering the cell content but need to advice it to skip generation
1775 ;; of background so that we later can easily move the emitted output.
1776 ;; In theory however shifting a cell vertically would be achieved by
1777 ;; modifying a cells vertical margins and have the incremental update
1778 ;; facility coping with it.
1782 (defclass* table-row-group ()
1783 style element
1784 rows)
1786 (defclass* table-row ()
1787 style element
1788 cells)
1790 (defclass* table-cell ()
1791 content ;a block box
1793 (colspan :initform 1)
1794 (rowspan :initform 1)
1795 (col-index :initform 0
1796 :documentation "The index of the column this cell is part of.")
1797 ;; This cells min/max width
1798 ;; That is the cells minimum or maximum width including all padding
1799 ;; or margin or border.
1801 ;; ### we should have the content width here and take a possible
1802 ;; 'width' value of the cell element into account.
1803 %minimum-width
1804 %maximum-width
1805 (minimum-height
1806 :documentation "what CSS calls the minimum height, that is the height of the cell content when rendered.")
1807 (baseline
1808 :documentation "The baseline of the rendered cell contents, relative coordinate from table-cell-y. This can as well be NIL incase the content really has no baseline.")
1809 (x :documentation "top-left device x coordinate either from outside the border (separate border model) or from the middle of the border (collapsing border model.")
1810 (y :documentation "see slot X")
1813 (defmethod table-cell-style ((cell table-cell))
1814 (block-box-style (table-cell-content cell)))
1816 (defmethod table-cell-element ((cell table-cell))
1817 (block-box-element (table-cell-content cell)))
1819 (defclass* table-column-group ()
1820 style
1821 element
1822 ;; hmm why do column groups bother with minmax?
1823 minimum-width
1824 maximum-width
1825 columns)
1827 (defclass* table-column ()
1828 style
1829 element
1830 (minimum-width :initform 0)
1831 (maximum-width :initform 0))
1833 (defclass* table ()
1834 style
1835 element
1836 decoration-output-record
1837 row-groups
1838 column-groups)
1840 (defun make-anonymous-element ()
1841 (sgml::make-pt/low :name :%anon))
1843 ;;;;
1845 ;; The interesting thing is that HTML tables are more powerful than
1846 ;; CSS2 tables. So does CSS2 for instance lack COLSPAN and ROWSPAN
1847 ;; attributes, I expect that these will be added in CSS3 or CSS4.
1849 ;; Columns get min/max widthen also
1851 ;; they probably do so by first absorbing the min/max of colspan=1
1852 ;; then we would look for colspan=2 cells and distribute the lack
1853 ;; equally.
1855 ;; Likewise rows need to get dimensions also. This gets a little bit
1856 ;; more complicate since rows now also get a kind of baseline.
1858 (defun table-column (table index)
1859 (loop for colgroup in (table-column-groups table) do
1860 (loop for col in (table-column-group-columns colgroup) do
1861 (when (zerop (prog1 index (decf index)))
1862 (return-from table-column col))))
1863 (error "Column number ~D is not existing." index))
1865 (defun table-number-of-columns (table)
1866 (reduce #'+ (mapcar (lambda (colgroup)
1867 (length (table-column-group-columns colgroup)))
1868 (table-column-groups table))))
1870 (defun table-number-of-rows (table)
1871 (reduce #'+ (mapcar (lambda (rowgroup)
1872 (length (table-row-group-rows rowgroup)))
1873 (table-row-groups table))))
1875 (defun table-number-of-rows (table)
1876 (let ((res 0))
1877 (map-table table (lambda (cell ri ci)
1878 (setf res (max res (+ ri (table-cell-rowspan cell))))))
1879 res))
1881 ;; ### now, this still is somewhat wrong ...
1883 (defun table-number-of-rows (table)
1884 (max
1885 (reduce #'+ (mapcar (lambda (rowgroup)
1886 (length (table-row-group-rows rowgroup)))
1887 (table-row-groups table)))
1888 (let ((res 0))
1889 (map-table table (lambda (cell ri ci)
1890 (setf res (max res (+ ri (table-cell-rowspan cell))))))
1891 res)))
1894 (defvar *table-depth* -1)
1896 (defparameter *table-depth-color*
1897 (list clim:+red+ clim:+blue+ clim:+green+ clim:+cyan+))
1899 ;;; Vertical align in a table
1901 ;; When a cell needs to be made larger than otherwise this is thought
1902 ;; to be accomplished by adjusting the cells top or bottom padding.
1903 ;; Also aligning a cell works this way.
1905 ;; To minimize movements of output records we want to establish row
1906 ;; heights in a fashion that cells spaning more than one row are only
1907 ;; accounted for when we reach a row in which they do not participate.
1909 (defun collect-table-2 (item)
1910 "Collects the table and also calculates the minima/maxima."
1911 (let ((table (collect-table item)))
1913 (loop for span from 1 to (table-number-of-columns table)
1915 (loop for row-group in (table-row-groups table) do
1916 (loop for row in (table-row-group-rows row-group) do
1917 (loop for cell in (table-row-cells row) do
1918 (let ((ci (table-cell-col-index cell)))
1919 (when (= (table-cell-colspan cell) span)
1920 ;; First, the cells extrema do not yet include the border.
1921 ;; ### separate border modell
1922 (let* ((border-space (+ (cooked-style-border-left-width (table-cell-style cell))
1923 (cooked-style-border-right-width (table-cell-style cell))))
1924 (min-cell (+ (table-cell-minimum-width cell)
1925 border-space))
1926 (max-cell (+ (table-cell-maximum-width cell)
1927 border-space))
1928 (have-border-spacing
1929 ;; amount of border-spacing gutter we can use
1930 (* (1- (table-cell-colspan cell))
1931 (table-horizontal-border-spacing table)))
1933 (min-yet (+ (loop for i from ci below (+ ci (table-cell-colspan cell))
1934 sum (table-column-minimum-width (table-column table i)))
1935 have-border-spacing))
1936 (max-yet (+ (loop for i from ci below (+ ci (table-cell-colspan cell))
1937 sum (table-column-maximum-width (table-column table i)))
1938 have-border-spacing))
1939 ;; how much is missing?
1940 (min-lack (max 0 (- min-cell min-yet)))
1941 (max-lack (max 0 (- max-cell max-yet))))
1942 (dotimes (i span)
1943 (incf (table-column-minimum-width (table-column table (+ ci i)))
1944 (/ min-lack span))
1945 (incf (table-column-maximum-width (table-column table (+ ci i)))
1946 (/ max-lack span))) )))))))
1947 table))
1949 (defun table-horizontal-border-spacing (table)
1950 "Shorthand accessor: Returns the table's horizontal inner cell gutter as defined by the CSS border-spacing property."
1951 (first (cooked-style-border-spacing (table-style table))))
1953 (defun table-vertical-border-spacing (table)
1954 "Shorthand accessor: Returns the table's vertical inner cell gutter as defined by the CSS border-spacing property."
1955 (second (cooked-style-border-spacing (table-style table))))
1957 ;; The cell extrema are measured from within the border inner edges. The column
1958 ;; widthen though are measured from the outside of the border. The extrema do
1959 ;; not include the border because the table renderer assigns the borders (in the
1960 ;; collapsed border model).
1962 ;; ### in the collapsing border model it is said that the border is rendered
1963 ;; straight on the grid lines. Also that the width of the table does not
1964 ;; include this border. So should the border hang over? Guess not.
1966 ;; ### we'll further unify the border models by splitting the border array into
1967 ;; two halfes. But be careful not to draw the border from two halfs.
1969 ;; ### fine now we only need to actually map the cellspacing and cellpadding to
1970 ;; the appropriate css attributes.
1972 (defun table-column-coordinates (table column-widths i &optional (span 1))
1973 "Return the left and right coordinates of the ith column of table, measured
1974 from outside the border and not including any gutter from border-spacing. The
1975 coordinates are measured relatively from the inner left of the table. Optionally
1976 a span count can be specified, in which case possible extra gutter because of
1977 border-spacing between the spaned columns is included."
1978 (let* ((x1 (+ (* (1+ i) (table-horizontal-border-spacing table)) ;off by one and one back
1979 (loop for j from 0 below i sum (elt column-widths j))))
1980 (x2 (+ x1
1981 (* (1- span) (table-horizontal-border-spacing table))
1982 (loop for j from i below (+ i span) sum (elt column-widths j)))))
1983 (values x1 x2)))
1985 (defun table-row-coordinates (table row-heights i &optional (span 1))
1986 "Analog to TABLE-COLUMN-COORDINATES."
1987 (let* ((y1 (+ (* (1+ i) (table-vertical-border-spacing table)) ;off by one and one back
1988 (loop for j from 0 below i sum (elt row-heights j))))
1989 (y2 (+ y1
1990 (* (1- span) (table-vertical-border-spacing table))
1991 (loop for j from i below (+ i span) sum (elt row-heights j)))))
1992 (values y1 y2)))
1994 (defun format-table (item x1 x2 yy style &aux (cur-depth *table-depth*))
1996 (with-simple-restart (skip "Skip rendering table at nesting depth ~D." (+ cur-depth 1))
1997 (setf (values x1 x2)
1998 (find-margins x1 x2 yy))
1999 (let ((*table-depth* (+ *table-depth* 1))
2000 (*baseline*-setp (not (null *baseline*)))) ;kludge
2002 (let ((table (collect-table-2 item)))
2003 ;; now setup the column minimum/maximum widthen.
2006 (let ((column-widths (allocate-table-columns table style x1 x2))
2007 (row-heights (loop repeat (table-number-of-rows table) collect 0)))
2009 (when *debug-tables*
2010 (format *trace-output* "~&=== we have a table at depth ~D.~%" *table-depth*)
2011 (format *trace-output* "~&=== column minima: ~S.~%"
2012 (loop for i below (table-number-of-columns table)
2013 collect (table-column-minimum-width (table-column table i))))
2014 (format *trace-output* "~&=== column maxima: ~S.~%"
2015 (loop for i below (table-number-of-columns table)
2016 collect (table-column-maximum-width (table-column table i))))
2017 (format *trace-output* "=== space available: ~D.~%" (- x2 x1))
2018 (format *trace-output* "=== allocated space: ~S.~%" column-widths)
2019 (finish-output *trace-output*))
2021 ;; Now we can actually figure out our real margins
2022 (let ((actual-width (+ (* (1+ (table-number-of-columns table)) (table-horizontal-border-spacing table))
2023 (reduce #'+ column-widths))))
2024 (setf (values x1 x2)
2025 (case (cooked-style-text-align style)
2026 (:right
2027 (values (- x2 actual-width) x2))
2028 (:center
2029 (values
2030 (+ x1 (floor (- (- x2 x1) actual-width) 2))
2031 (- x2 (ceiling (- (- x2 x1) actual-width) 2))))
2033 (values x1 (+ x1 actual-width))))))
2036 (let ((bg-record (clim:with-new-output-record (clim-user::*pane*))))
2037 (setf (table-decoration-output-record table) bg-record)
2038 (let ((yyy yy)
2039 (dangling-cells nil)) ;a list of (rowspan total-rowspan cell) pairs of cells whose row span
2040 ; was larger than one. rowspan is decremented each row and
2041 ; every pair yielding a zero rowspan will be considered for
2042 ; row height calculation.
2043 (loop for row in (and (table-row-groups table) (table-row-group-rows (first (table-row-groups table))) )
2044 for ri from 0 do
2045 (loop for cell in (table-row-cells row) do
2046 (let* ((ci (table-cell-col-index cell)))
2047 (multiple-value-bind (xx1 xx2)
2048 (table-column-coordinates table column-widths ci (table-cell-colspan cell))
2049 (let* ((x1 (+ x1 xx1))
2050 (w (- xx2 xx1)))
2051 ;; aha, i guess that we need to nuke style
2052 ;; here ... since width now is relative to
2053 ;; containig block.
2054 ;; ###
2055 (setf (slot-value (table-cell-style cell) 'css::width)
2056 (- w (cooked-style-padding-left (table-cell-style cell))
2057 (cooked-style-padding-right (table-cell-style cell))))
2059 (let ((*baseline* nil)
2061 (multiple-value-bind (vm+ vm- yy yy0)
2062 (let ((*floating-boxes* nil)) ;### hmm
2063 (multiple-value-prog1 (format-block (table-cell-content cell)
2064 x1 (+ x1 w)
2065 nil ;ss
2066 nil ;before-markers
2067 0 0 ;vmargins
2068 yyy)
2069 (setf fl *floating-boxes*)))
2070 (incf yy vm+) ;hmm?
2071 (incf yy vm-) ;hmm?
2072 (setf yy (max yy (reduce #'max (mapcar #'floating-chunk-y2 fl) :initial-value yy)))
2073 (setf (table-cell-x cell) x1)
2074 (setf (table-cell-y cell) yyy)
2075 (setf (table-cell-baseline cell) *baseline*);;(if *baseline* (- *baseline* yyy) 0))
2076 (setf (table-cell-minimum-height cell) (- yy yyy))
2077 (cond ((= (table-cell-rowspan cell) 1)
2078 (setf (elt row-heights ri) (max (elt row-heights ri) (table-cell-minimum-height cell))))
2080 (push (list (table-cell-rowspan cell) (table-cell-rowspan cell) cell)
2081 dangling-cells)))
2082 (when *debug-tables*
2083 (unless (or (= x1 (+ x1 w))
2084 (= yyy yy))
2085 #-NIL
2086 (clim:draw-rectangle* clim-user::*pane*
2087 x1 yyy (+ x1 w) yy
2088 :ink (elt *table-depth-color*
2089 (mod *table-depth* (length *table-depth-color*)))
2090 :filled nil))))) ))))
2091 (setf dangling-cells
2092 (mapcan (lambda (x)
2093 (cond ((= 1 (car x))
2094 (let* ((have (loop for i from ri above (max 0 (- ri (second x))) sum (elt row-heights i)))
2095 (want (table-cell-minimum-height (third x)))
2096 (lack (max 0 (- want have))))
2097 (when (> want have)
2098 (loop for i from ri above (max 0 (- ri (second x))) do
2099 (incf (elt row-heights i) (/ lack (second x))))))
2100 nil)
2102 (list (cons (- (car x) 1) (cdr x))))))
2103 dangling-cells))
2105 (incf yyy (elt row-heights ri))
2106 finally
2107 ;; now some cells might be left
2108 (mapcan (lambda (x)
2109 (let* ((have (loop for i from ri above (max 0 (- ri (second x))) sum (elt row-heights i)))
2110 (want (table-cell-minimum-height (third x)))
2111 (lack (max 0 (- want have))))
2112 (when (> want have)
2113 (loop for i from ri above (max 0 (- ri (second x))) do
2114 (incf (elt row-heights i) (/ lack (second x)))))))
2115 dangling-cells)
2117 ;; Grrff
2119 ;; Let us redo the row heights
2120 ;; [This is neccessary because the above is just a rough approximation]
2122 ;; Every cell has a minimum-height (actual height of its content) and a baseline.
2124 (let ((row-baselines (loop repeat (table-number-of-rows table) collect 0)))
2125 (setf row-heights (loop repeat (table-number-of-rows table) collect 0))
2126 ;; First establish the baseline of each row
2127 (map-table table (lambda (cell ri ci)
2128 (when (and (eql :baseline (cooked-style-vertical-align (table-cell-style cell)))
2129 (table-cell-baseline cell))
2130 (setf (elt row-baselines ri)
2131 (max (elt row-baselines ri)
2132 (table-cell-baseline cell))))))
2133 ;; Establish row-heights. Take care of baseline aligned cells.
2134 (loop for rs from 1 to (table-number-of-rows table) do
2135 (map-table table (lambda (cell ri ci)
2136 (when (= (table-cell-rowspan cell) rs)
2137 (let* ((have (+ (loop for i from ri below (+ ri rs) sum (elt row-heights i))
2138 (* (1- (table-cell-rowspan cell))
2139 (table-vertical-border-spacing table))))
2140 (want (cond ((and (eql :baseline (cooked-style-vertical-align (table-cell-style cell)))
2141 (not (null (table-cell-baseline cell))))
2142 (+ (elt row-baselines ri)
2143 (- (table-cell-minimum-height cell)
2144 (table-cell-baseline cell))))
2146 (table-cell-minimum-height cell))))
2147 (lack (max 0 (- want have))))
2148 (when (> want have)
2149 (loop for i from ri below (+ ri rs) do
2150 (incf (elt row-heights i) (/ lack rs)))))))))
2152 (setf yyy (+ yy (reduce #'+ row-heights)
2153 (* (1+ (length row-heights)) (table-vertical-border-spacing table))))
2155 ;; Redo background and align cells
2156 ;; But: This is not entirely correct!
2157 (map-table table
2158 (lambda (cell ri ci)
2159 (let ((cell-record (block-box-output-record (table-cell-content cell)))
2160 (bg-record (block-box-decoration-output-record (table-cell-content cell))))
2161 (multiple-value-bind (y1 y2)
2162 (table-row-coordinates table row-heights ri (table-cell-rowspan cell))
2163 (incf y1 yy)
2164 (incf y2 yy)
2166 (let* ((rh (- y2 y1))
2168 (case (cooked-style-vertical-align (table-cell-style cell))
2169 (:bottom
2170 (- rh (table-cell-minimum-height cell)))
2171 (:middle
2172 (floor (- rh (table-cell-minimum-height cell)) 2))
2173 (:baseline
2174 (cond ((not (null (table-cell-baseline cell)))
2175 (- (elt row-baselines ri) (table-cell-baseline cell)))
2177 (warn "Funny, cell has baseline aligning, but no baseline.")
2178 ;; threat this as top
2179 0)))
2180 (otherwise
2181 0)))
2182 (y-soll ;where do we want this cell?
2183 (+ y1
2184 dy)))
2185 ;; CLIM makes no guarantees about an output records position, so
2186 ;; we only can move relative.
2187 (multiple-value-bind (x y) (clim:output-record-position cell-record)
2188 (setf (clim:output-record-position cell-record)
2189 (values x (+ y (- y-soll (table-cell-y cell)))))))
2191 (clim:clear-output-record bg-record)
2192 (multiple-value-bind (xx1 xx2) (table-column-coordinates table column-widths ci (table-cell-colspan cell))
2193 (let ((new-record
2194 (clim:with-output-recording-options (clim-user::*pane* :record t :draw nil)
2195 (clim:with-new-output-record (clim-user::*pane*)
2196 (draw-box-decoration clim-user::*pane* (+ x1 xx1) y1 (+ x1 xx2) y2
2197 (block-box-style (table-cell-content cell)))))))
2198 (clim:delete-output-record new-record (clim:output-record-parent new-record))
2199 (clim:add-output-record new-record bg-record)))))))
2202 ;; draw the tables background
2203 (let* ((y1 yy)
2204 (y2 yyy)
2205 (x1 x1)
2206 (x2 x2))
2207 (let ((new-record
2208 (clim:with-output-recording-options (clim-user::*pane* :record t :draw nil)
2209 (clim:with-new-output-record (clim-user::*pane*)
2210 (draw-box-decoration clim-user::*pane* x1 y1 x2 y2
2211 (table-style table))))))
2212 (clim:delete-output-record new-record (clim:output-record-parent new-record))
2213 (clim:add-output-record new-record bg-record)))
2215 ;; draw borders
2216 '(multiple-value-bind (hborders vborders) (table-borders table)
2217 ;; horizontal borders
2218 (loop for i from 0 below (array-dimension hborders 0) do
2219 (loop for j from 0 below (array-dimension hborders 1) do
2220 (destructuring-bind (origin style width color) (aref hborders i j)
2221 (declare (ignore origin))
2222 ;; This is the border between row (i-1) and row i running across column j
2223 (unless (eql style :none)
2224 (multiple-value-bind (x1 x2) (table-column-coordinates table column-widths j)
2225 (let* (
2226 (y1 (+ yy (loop for k below i sum (elt row-heights k)))))
2227 (clim:draw-line* clim-user::*pane*
2228 x1 y1 x2 y1
2229 :ink (ws/x11::parse-x11-color color)
2230 :line-thickness width)))))))
2231 ;; vertical borders
2232 (loop for i from 0 below (array-dimension vborders 0) do
2233 (loop for j from 0 below (array-dimension vborders 1) do
2234 (destructuring-bind (origin style width color) (aref vborders i j)
2235 (declare (ignore origin))
2236 ;; This is the border between column (j-1) and column j running across row i
2237 (unless (eql style :none)
2238 (let* ((y1 (+ yy (loop for k below i sum (elt row-heights k))))
2239 (y2 (+ y1 (elt row-heights i)))
2240 (x1 (+ x1 (loop for k below j sum (elt column-widths k)))))
2241 (clim:draw-line* clim-user::*pane*
2242 x1 y1 x1 y2
2243 :ink (ws/x11::parse-x11-color color)
2244 :line-thickness width)))))) )
2245 ;; Kludge, in our book a table also has a baseline. We set it up manually, since
2246 ;; we moved the rendered output of table cells.
2247 ;; ### only that this now spoils the telepolis site ;(
2248 ;; ### i assume that tables have no baseline. so we really need to account for
2249 ;; cells which do not have a baseline and align them top.
2250 ;; ### and should cells which are not baseline aligned be accounted for at all?
2251 ;; #+NIL
2252 (and row-baselines
2253 (unless *baseline*-setp
2254 (setf *baseline* (+ yy (elt row-baselines 0)))))
2256 yyy)))))))
2257 yy))
2259 (defun map-table (table continuation)
2260 "Applies 'continuation' to every table cell and its gird position"
2261 (loop for row-group in (table-row-groups table) do
2262 (loop for row in (table-row-group-rows row-group)
2263 for ri from 0 do
2264 (loop for cell in (table-row-cells row) do
2265 (funcall continuation cell ri (table-cell-col-index cell))))))
2268 (defun allocate-table-columns (table style x1 x2)
2269 (let ((table.width (slot-value style 'css::width))
2270 (ncols (table-number-of-columns table)))
2271 (setf table.width (maybe-resolve-percentage table.width (- x2 x1)))
2272 (let* ((mins (loop for i below (table-number-of-columns table)
2273 collect (table-column-minimum-width (table-column table i))))
2274 (maxs (loop for i below (table-number-of-columns table)
2275 collect (table-column-maximum-width (table-column table i))))
2276 (min (reduce #'+ mins))
2277 (max (reduce #'+ maxs))
2278 (gutter (* (1+ (table-number-of-columns table)) (table-horizontal-border-spacing table))))
2280 (setf table.width
2281 (cond
2282 ;; | 2. If the 'table' or 'inline-table' element has 'width: auto', the
2283 ;; | computed table width is the greater of the table's containing block
2284 ;; | width and MIN. However, if the maximum width required by the
2285 ;; | columns plus cell spacing or borders (MAX) is less than that of the
2286 ;; | containing block, use MAX.
2287 ;; ### now this somehow got lost
2288 ;; ### what really needs to be done: Our accessors
2289 ;; compute the dimensions for us, so they should compute
2290 ;; in this case too.
2291 ((eql table.width :auto)
2292 (when *debug-tables*
2293 (format *trace-output* "~&Using auto layout (table.width = ~d) ~%" table.width))
2294 (if (< (+ max gutter) (- x2 x1))
2295 (+ max gutter)
2296 (max (- x2 x1)
2297 min)))
2299 ;; CSS-2 17.5.2:
2300 ;; | 1. If the 'table' or 'inline-table' element's 'width' property has a
2301 ;; | specified value (W) other than 'auto', the property's computed
2302 ;; | value is the greater of W and the minimum width required by all the
2303 ;; | columns plus cell spacing or borders (MIN). If W is greater than
2304 ;; | MIN, the extra width should be distributed over the columns.
2305 ((realp table.width)
2306 (when *debug-tables*
2307 (format *trace-output* "~&Using fixed layout~%"))
2308 (max table.width
2309 min) )
2311 (error "BARF"))))
2312 (decf table.width gutter)
2314 (let* ((excess (- table.width min))
2315 (deltas (loop for min in mins for max in maxs collect (- max min)))
2316 (delta-sum (reduce #'+ deltas))
2317 (res
2318 (loop for delta in deltas
2319 for min in mins
2320 for max in maxs
2321 collect
2322 (if (zerop delta-sum)
2323 (+ min (* excess (/ (length mins)))) ;### hmm
2324 (+ min (* excess delta (/ delta-sum)))))))
2325 res))))
2327 (defun map-table-cells (function table)
2328 "Apply function 'function' to each of the cells of the table 'table'."
2329 (dolist (row-group (table-row-groups table))
2330 (dolist (row (table-row-group-rows row-group))
2331 (dolist (cell (table-row-cells row))
2332 (funcall function cell)))))
2334 (defun table-cell-minimum-width (cell)
2335 (with-slots (%minimum-width) cell
2336 (unless %minimum-width
2337 (table-cell-setup-min/max-width cell))
2338 %minimum-width))
2340 (defun table-cell-maximum-width (cell)
2341 (with-slots (%maximum-width) cell
2342 (unless %maximum-width
2343 (table-cell-setup-min/max-width cell))
2344 %maximum-width))
2346 (defun table-cell-setup-min/max-width (cell)
2347 (with-slots (%minimum-width %maximum-width) cell
2348 (setf (values %minimum-width %maximum-width)
2349 (minmax-block (table-cell-content cell)))))
2351 (defun collect-table (item)
2352 (let ((row-groups nil)
2353 (col-groups nil))
2354 (assert (and (block-box-p item)
2355 (eq (cooked-style-display (block-box-style item)) :table)))
2356 (let ((cur-col-group nil))
2357 (labels ((flush-cur-col-group ()
2358 (when cur-col-group
2359 (push (make-instance 'table-column-group
2360 :columns (reverse cur-col-group))
2361 col-groups)
2362 (setf cur-col-group nil))))
2363 (dolist (x (block-box-content item))
2364 (when (block-box-p x)
2365 (case (cooked-style-display (block-box-style x))
2366 ((:table-row-group
2367 :table-header-group
2368 :table-footer-group)
2369 (push (collect-row-group x) row-groups))
2370 (:table-column-group
2371 (flush-cur-col-group)
2372 (push (collect-column-group x) col-groups))
2373 (:table-column
2374 (push (collect-column x) cur-col-group)))))
2375 (flush-cur-col-group)))
2377 ;;; Assign the col-index slots of cells
2378 (let ((row-allocation nil)) ;a list of integers indicating allocated rows.
2379 ; when a new cell with a certain rowspan r at
2380 ; column i is encountered we enter r at the i'th
2381 ; element of this list
2382 ;; Note: it feels a bit strange that cells can span across a I
2383 ;; expect that to be fixed by a later version; and check what
2384 ;; HTML does there.
2386 ;; ### if some guy passes in some pretty large colspan we lose.
2387 ;; We can fight that by run-length encode (sic!) the
2388 ;; row-allocation vector.
2389 (loop for row-group in row-groups do
2390 (loop for row in (table-row-group-rows row-group) do
2391 ;; we enter a new row, so decrement the row-allocation
2392 (setf row-allocation (mapcar (lambda (x) (max 0 (- x 1))) row-allocation))
2394 (labels ((safe-subseq (seq start &optional end)
2395 (let ((l (length seq)))
2396 (subseq seq (min start l) (and end (min end (length seq))))))
2397 (allocate-row (cells row-allocation col-index)
2398 "Allocate cells, returns the new row allocation vector"
2399 (cond ((null cells)
2400 row-allocation)
2401 ;; does the cell fit?
2402 ((every #'zerop
2403 (safe-subseq row-allocation 0 (table-cell-colspan (car cells))))
2404 (let ((cell (car cells)))
2405 ;; It does, so store col-index and stuff the rowspan into the vector.
2406 (setf (table-cell-col-index cell) col-index)
2407 (append (make-list (table-cell-colspan cell) :initial-element (table-cell-rowspan cell))
2408 (allocate-row (cdr cells)
2409 (safe-subseq row-allocation (table-cell-colspan cell))
2410 (+ col-index (table-cell-colspan cell))))))
2412 ;; It does not fit, try the next column
2413 (cons (car row-allocation)
2414 (allocate-row cells (cdr row-allocation) (+ col-index 1)))))))
2416 (setf row-allocation
2417 (allocate-row (table-row-cells row) row-allocation 0))))) )
2422 ;; we need now two sanity measures:
2423 ;; a. cut excessive colspan.
2424 ;; b. cut excessive rowspan.
2425 ;; c. make additional rows needed.
2426 ;; d. make additional columns needed.
2428 (setf row-groups (reverse row-groups))
2429 (let ((need 0))
2430 (dolist (row-group row-groups)
2431 (dolist (row (table-row-group-rows row-group))
2432 (dolist (cell (table-row-cells row))
2433 (setf need (max need (+ (table-cell-col-index cell)
2434 (table-cell-colspan cell)))))))
2436 ;; Sanity check: We could the columns by column groups and by
2437 ;; colspan. When there is a difference => Report the error and fix
2438 ;; it.
2440 ;; Count the columns accounted for by existing column-groups
2441 (let ((have (reduce #'+ (mapcar #'length (mapcar #'table-column-group-columns col-groups)))))
2442 (cond ((< have need)
2443 ;; add an extra column group
2444 (unless (zerop have)
2445 (warn "Columns and/or columns groups were explicitly specified yet ~
2446 do not match actual number of columns."))
2447 (push (make-instance 'table-column-group
2448 :columns
2449 (loop repeat (- need have) collect
2450 (make-instance 'table-column)))
2451 col-groups))
2452 ((> have need)
2453 (warn "Columns and/or columns groups were explicitly specified yet there actually less columns."))))
2455 (setf col-groups (reverse col-groups)))
2456 (make-instance 'table
2457 :style (block-box-style item)
2458 :element (block-box-element item)
2459 :row-groups row-groups
2460 :column-groups (reverse col-groups))))
2462 (defun collect-row-group (item)
2463 (make-instance 'table-row-group
2464 :style (block-box-style item)
2465 :element (block-box-element item)
2466 :rows (mapcar (lambda (x)
2467 (assert (and (block-box-p x)
2468 (eq (cooked-style-display (block-box-style x)) :table-row)))
2469 (collect-row x))
2470 (block-box-content item))))
2472 (defun collect-row (item)
2473 (make-instance 'table-row
2474 :style (block-box-style item)
2475 :element (block-box-element item)
2476 :cells (mapcar (lambda (x)
2477 (assert (and (block-box-p x)
2478 (eq (cooked-style-display (block-box-style x)) :table-cell)))
2479 (collect-cell x))
2480 (block-box-content item))))
2482 (defun collect-cell (item)
2483 (make-instance 'table-cell
2484 :colspan (pt-attr/integer (block-box-element item) :colspan 1)
2485 :rowspan (pt-attr/integer (block-box-element item) :rowspan 1)
2486 :content item))
2488 (defun collect-column-group (item)
2489 (make-instance 'table-column-group
2490 :style (block-box-style item)
2491 :element (block-box-element item)
2492 :columns (mapcar (lambda (x)
2493 (collect-column x))
2494 (block-box-content item))))
2496 (defun collect-column (item)
2497 (assert (and (block-box-p item)
2498 (eq (cooked-style-display (block-box-style item)) :table-column)))
2499 (make-instance 'table-column
2500 :style (block-box-style item)))
2503 ;;;;
2505 (defun minmax-block (block-box)
2506 (minmax-block-content (block-box-content block-box) (block-box-style block-box)))
2508 (defun minmax-block-content (items block-style)
2509 (let ((min 0)
2510 (max 0))
2511 (let* ((s block-style))
2512 (multiple-value-bind (ml bl pl wd pr br mr) (resolve-widthen s 0)
2513 ;; ### we need a more throughly analysis here.
2514 ;; ### also it is questionable if a block which specifies its
2515 ;; width should have different min/max widthen altogether.
2517 ;; ### Now this was a stupid idea, since the sum of
2518 ;; resolve-widthen is always 0 (what we gave as
2519 ;; argument for containing block width).
2520 (setf ml 0 mr 0) ;xxx (s.a.)
2522 ;; Further more if this happens to be a table cell, we do not include
2523 ;; the border of the cell as the border is set up by the table
2524 ;; typesetter. Yes, I know this is kludgey.
2525 (cond ((eql :table-cell (cooked-style-display block-style))
2526 (setf bl 0 br 0)))
2528 (when *debug-tables*
2529 (format *trace-output* "~&~S (~S): ml=~s, bl=~s, pl=~s, wd=~s, pr=~s, br=~s, mr=~s~%"
2530 'minmax-block-content block-style ml bl pl wd pr br mr))
2531 (dolist (item items)
2532 (etypecase item
2533 (para-box
2534 (multiple-value-bind (mi ma) (minmax-para (para-box-items item) block-style)
2535 (setf min (max min mi))
2536 (setf max (max max ma))))
2537 (marker-box
2538 ;; ### hmm
2540 (block-box
2541 (case (cooked-style-display (block-box-style item))
2542 (:table
2543 (multiple-value-bind (mi ma) (minmax-table item (block-box-style item))
2544 (setf min (max min mi)
2545 max (max max ma))))
2547 (multiple-value-bind (mi ma)
2548 (minmax-block-content (block-box-content item) (block-box-style item))
2549 (setf min (max min mi))
2550 (setf max (max max ma))))))))
2552 (let ((explicit-width (slot-value block-style 'css::width)))
2553 (when (realp explicit-width)
2554 (setf min (max min explicit-width)
2555 max (max min explicit-width))))
2557 (values (+ min ml bl pl pr br mr)
2558 (+ max ml bl pl pr br mr))))))
2560 (defun minmax-table (item block-style)
2561 (let ((table (collect-table-2 item)))
2562 (multiple-value-bind (min max)
2563 (values
2564 (loop for i below (table-number-of-columns table)
2565 sum (table-column-minimum-width (table-column table i)))
2566 (loop for i below (table-number-of-columns table)
2567 sum (table-column-maximum-width (table-column table i))))
2568 (incf min (* (1+ (table-number-of-columns table)) (table-horizontal-border-spacing table)))
2569 (incf max (* (1+ (table-number-of-columns table)) (table-horizontal-border-spacing table)))
2570 (let ((explicit-width (slot-value block-style 'css::width)))
2571 (when (realp explicit-width)
2572 (setf min (max min explicit-width)
2573 max (max min explicit-width))))
2574 (values min max))))
2576 (defun minmax-para (items block-style)
2577 (let ((clw 0)
2578 (cww 0)
2579 (min 0)
2580 (max 0) ;max is simply the sum.
2581 (cur-line nil)
2582 (cur-word nil)
2583 (ss (list block-style)))
2584 ;; ### we shouldn't borrow that blindly from format-para
2585 ;; ### replaced object might have percentage widthen
2586 (labels ((flush-line ()
2587 (setf min (max min (reduce #'+ (mapcar #'chunk-width cur-line))))
2588 (setf cur-line nil
2589 clw 0))
2590 (flush-word ()
2591 (dolist (k (reverse cur-word)) (push k cur-line))
2592 (setf cur-word nil)
2593 (incf clw cww)
2594 (setf cww 0))
2596 (process (xs)
2597 (let ((x (car xs)))
2598 (typecase x
2599 (open-chunk
2600 (incf max (chunk-width x))
2601 (push (bounding-chunk-style x) ss)
2602 (incf cww (chunk-width x))
2603 (push x cur-word))
2604 (close-chunk
2605 (incf max (chunk-width x))
2606 (pop ss)
2607 (incf cww (chunk-width x))
2608 (push x cur-word))
2609 (disc-chunk
2610 ;; ### we assume a whole lot about disc-chunk-here
2611 (incf max (reduce #'+ (mapcar #'chunk-width (remove-if #'disc-chunk-p (disc-chunk-here x))))) ;###
2612 (flush-word)
2613 (let ((p (disc-chunk-here x))
2614 (ww 0))
2615 (block zulu
2616 (dolist (k p)
2617 (cond ((typep k 'disc-chunk)
2618 ;; before is missing.
2619 ;; actually this is a stranger situation ...
2620 (return-from zulu))
2621 ;; ###
2622 ((typep k 'floating-chunk)
2623 nil)
2625 (incf ww (chunk-width k))) ))
2626 ;; disc exhausted continue with list
2627 (dolist (k (cdr xs))
2628 (cond ((typep k 'disc-chunk)
2629 ;; before is missing.
2630 ;; actually this is a stranger situation ...
2631 (return-from zulu))
2632 ;; ###
2633 ((typep k 'floating-chunk)
2634 nil)
2636 (incf ww (chunk-width k))))))
2637 (show (disc-chunk-before x))
2638 (flush-word)
2639 (flush-line)
2640 (show (disc-chunk-after x))))
2641 (replaced-object-chunk
2642 (let ((w (chunk-width x)))
2643 (when *debug-tables*
2644 (format *trace-output* "~&~S: ~S = ~S~%"
2645 'minmax-para `(chunk-width ,x) (chunk-width x)))
2646 (setf w (ro/size (replaced-object-chunk-object x)))
2647 (incf max w)
2648 (incf cww w)
2649 (push x cur-word)))
2650 (kern-chunk
2651 (incf max (chunk-width x))
2652 (incf cww (chunk-width x))
2653 (push x cur-word))
2654 (black-chunk
2655 (incf max (chunk-width x))
2656 (incf cww (chunk-width x))
2657 (push x cur-word))
2658 (floating-chunk
2659 (compute-floating-chunk-width x 0)
2660 (incf max (floating-chunk-width x))
2661 (setf min (max min (floating-chunk-width x))))
2665 (show (seq)
2666 (do ((q seq (cdr q)))
2667 ((null q))
2668 (setf q (process q)) )) )
2669 (show items)
2670 (flush-word)
2671 (flush-line))
2672 (values min max)) )
2674 (defun draw-box-decoration (medium x1 y1 x2 y2 style
2675 &key (left-halfp t) (right-halfp t))
2676 ;; die coordinaten sind ganz innen.
2677 (clim-draw-background medium
2678 x1 y1 x2 y2
2679 (cooked-style-background-color style))
2680 (clim-draw-border medium
2681 x1 y1 x2 y2
2682 (cooked-style-border-top-width style)
2683 (cooked-style-border-top-style style)
2684 (cooked-style-border-top-color style)
2685 (if right-halfp (cooked-style-border-right-width style) 0)
2686 (cooked-style-border-right-style style)
2687 (cooked-style-border-right-color style)
2688 (cooked-style-border-bottom-width style)
2689 (cooked-style-border-bottom-style style)
2690 (cooked-style-border-bottom-color style)
2691 (if left-halfp (cooked-style-border-left-width style) 0)
2692 (cooked-style-border-left-style style)
2693 (cooked-style-border-left-color style)))
2696 ;;;;
2699 (defun floating-chunk-width (chunk)
2700 (or (slot-value chunk '%width)
2701 (error "FLOATING-CHUNK has no width?")))
2703 (defun compute-floating-chunk-width (chunk containing-block-width)
2704 ;;; Kludge!
2705 #+NIL
2706 (cond ((and (block-box-p (floating-chunk-content chunk))
2707 (eq (element-gi (block-box-element (floating-chunk-content chunk)))
2708 :table))))
2709 (setf (slot-value chunk '%width)
2710 (let* ((style (floating-chunk-style chunk))
2711 (ml (cooked-style-margin-left style))
2712 (pl (cooked-style-padding-left style))
2713 (bl (cooked-style-border-left-width style))
2714 (wd (cooked-style-width style))
2715 (br (cooked-style-border-right-width style))
2716 (pr (cooked-style-padding-right style))
2717 (mr (cooked-style-margin-right style)))
2718 ;; kludge
2719 (cond ((eq :auto (slot-value style 'css::width))
2720 (setf wd (minmax-block (floating-chunk-content chunk)))))
2721 (when *debug-tables*
2722 (print (list 'compute-floating-chunk-width
2723 (cooked-style-display (block-box-style (floating-chunk-content chunk)))
2724 (cooked-style-display style)
2725 ml pl bl wd br pr mr)
2726 *trace-output*)
2727 (finish-output *trace-output*))
2728 (+ ml pl bl wd br pr mr))))
2731 ;;;;
2733 (defun flatten-code (form)
2734 (ecase (car form)
2735 ((case ecase)
2736 (let ((temp 'aux)
2737 (labels (mapcar (lambda (x) (gentemp "L.")) (cddr form)))
2738 (raus (gentemp "L.")))
2739 (values
2741 (setf ,temp ,(cadr form))
2742 ,@(mapcar (lambda (clause label)
2743 (cond ((atom (car clause))
2744 `(when (eql ,temp ',(car clause)) (go ,label)))
2746 `(when (member ,temp ',(car clause)) (go ,label))))
2748 (cddr form) labels)
2749 ,@(mapcan (lambda (clause label)
2750 (append (list label)
2751 (copy-list (cdr clause))
2752 (list `(go ,raus))))
2753 (cddr form) labels)
2754 ,raus))))))
2757 (defun cooked-style-block-element-p (cooked-style)
2758 (member (cooked-style-display cooked-style)
2759 '(:block :list-item
2760 :table :table-row :table-header :table-row-group :table-footer
2761 :table-column :table-column-group :table-cell
2762 :table-caption)))
2765 (defun po (seq ignore)
2766 (popo-block seq nil nil))
2768 (defun element-warn (element format &rest args)
2769 (declare (ignore element))
2770 (apply #'warn format args))
2772 (defun popo-block (q ss cbss)
2773 ;; popo-block now returns a block-box
2774 (let (res nres me mes
2775 before-markers)
2776 ;; first off the first thing must be a block-open
2777 (unless (eq (caar q) :open)
2778 (error "Barf! (1)"))
2779 (push (my-setup-style (cadar q) (car ss) cbss) ss)
2781 (setf mes (car ss))
2782 (unless (cooked-style-block-element-p (car ss))
2783 (error "Barf! (2) -- Expected cooked-style-block-element, found ~A"
2784 (cooked-style-display (car ss))))
2785 (setf me (cadar q))
2786 (pop q)
2788 ;; Look at a possible :before element.
2789 ;; ### look for a way to refactor that for the :after element also.
2790 ;; Q: can generated content float? I mean if it floats it must be of display:block;
2791 ;; Q: why is display:none; ruled out?
2792 ;;; Note
2794 ;; CSS2 has this funny restriction that a marker box can only emerge from
2795 ;; generated content. It would be better [for a possible typesetter] if we
2796 ;; allow marker boxen to emerge from real element trees so that they can
2797 ;; have more rich content.
2799 (unless (typep me 'pseudo-element) ;pseudo elements itself have no generated content
2800 (let* ((pe (if (eql (cooked-style-display (car ss)) :list-item)
2801 (make-list-item-marker-element me (car ss))
2802 (make-instance 'before-pseudo-element :proxee me)))
2803 ;; ### what is the containing-block of such a pseudo-element?
2804 (pe-style (my-setup-style pe (car ss) cbss)))
2805 ;; ### now list items are supposed to map into such before/after elements also ...
2806 (unless (null (cooked-style-content pe-style))
2807 (let ((display (cooked-style-display pe-style)))
2808 (unless (member display '(:block :inline :marker))
2809 (element-warn "display property is ~S treated as ~S."
2810 display :block)
2811 ;; xxx -- we should not modify a cooked-style.
2812 ;; When we want such a rule then during cooking!
2813 (setf (cooked-style-display pe-style)
2814 (setf display :block)))
2816 (case display
2817 ((:block)
2818 ;; We do as when another block occurred here. Implementation: make
2819 ;; recursive call to popo-block. Watch out for possible double
2820 ;; application of before/after.
2822 ((:inline)
2823 ;; We just do as when the appropriate inline element appeared here.
2824 ;; To achieve that we must somehow enter paragraph context and call
2825 ;; popo-para. Possible complication: white-space convention is
2826 ;; slightly off for generated content. and: avoid double
2827 ;; application of :before/:after. (Although X:before:before would
2828 ;; be logical ;-)
2830 ;; we achieve that by just pushing the generated content onto our
2831 ;; input.
2832 (push (list :close pe) q)
2833 ;; Note: due to an evil hack in popo-para, we do not add the
2834 ;; content itself since it will then be handled by an appropriate
2835 ;; clause in popo-para. Cleaner would be to add another
2836 ;; 'pre-chunk' stating this is content.
2837 (push (list :open pe) q) )
2838 ((:marker)
2839 ;; Markers are collected for the next paragraph that passes by
2840 ;; since they are then included into para-box so that the
2841 ;; paragraph breaking algorithm can see the marker too. This
2842 ;; has to be this way because the marker participates in the
2843 ;; line-height/vertical-alignment calculation of the first/last
2844 ;; line of the paragraph.
2845 ;; they are to be regarded as inline elements I guess ..
2846 ;; Q: Is there any first-line/first-letter stuff?
2847 ;; I guess not: It would simply not look right ...
2848 ;; oops it is indeed fine to attach them here, only that we are clueless
2849 ;; about the margin ... and it depends on the block we attached that to.
2850 ;; so alternate route: add markers to the block and handle them further up.
2852 ;; another alternative:
2853 ;; link from the marker box to its block.
2854 ;; when formatting we can peek there for the margin.
2856 ;; ### even if content is not-null popo-para might still return NIL.
2857 (let ((content (popo-para
2858 (list (list :open pe) (list :close pe))
2861 cbss
2862 :run :invariant ;we do not want first-line stuff applied here
2864 (cond ((null content)
2865 ;; ### what does that mean?
2868 (push
2869 (make-marker-box
2870 :style pe-style
2871 :side :left
2872 :content content)
2873 res))))))
2874 ;; we probably can cope with compact boxen in a similar way?
2875 ;; Now, a compact box that fits is like a marker box?
2876 ;; yes, they are.
2877 ))))
2881 (tagbody
2883 (when (null q)
2884 ;; stuff just ended
2885 (error "Barf 4") )
2887 (let* ((kind (caar q)) (data (cadar q))
2888 (s (and (member kind '(:open :close))
2889 (if (eq data me)
2891 (my-setup-style data (car ss) mes)))))
2893 (cond ((and (eq kind :open)
2894 (and (cooked-style-block-element-p s)
2895 (eq (cooked-style-float s) :none)))
2896 ;; another block goes open
2897 (setf (values nres q) (popo-block q ss #| containing block style: |# mes))
2898 (push nres res)
2899 (go L.0))
2901 ((and (eq kind :close)
2902 (cooked-style-block-element-p s))
2903 ;; this must be our block
2905 (unless (eq (slot-value (car ss) 'css::%element)
2907 (error "buuz"))
2908 (return-from popo-block
2909 (values (make-block-box
2910 :element me
2911 :style (car ss)
2912 :content (reverse res))
2913 (cdr q))))
2915 ((and (eq kind :open)
2916 (eq (cooked-style-display s) :none))
2917 (setf q (member-if (lambda (y) (eq (second y) data)) (cdr q))) )
2919 ((or (and (member kind '(:open :close))
2920 (or (eq (cooked-style-display s) :inline)
2921 (not (eq (cooked-style-float s) :none))))
2922 (eq kind :data))
2923 (setf (values nres q) (popo-para q ss me mes))
2924 (dolist (k nres) (push k res))
2925 (go L.0))
2928 (error "Unexpected item: ~S." (car q))) ))
2930 L.continue
2931 (setf q (cdr q))
2932 (go L.0)) ))
2934 (defun make-open-chunk (element style)
2935 (make-instance 'open-chunk :style style :pt element))
2937 (defun make-close-chunk (element style)
2938 (make-instance 'close-chunk :style style :pt element))
2940 (defun make-half-open-chunk (element style)
2941 (make-instance 'open-chunk :halfp t :style style :pt element))
2943 (defun make-half-close-chunk (element style)
2944 (make-instance 'close-chunk :halfp t :style style :pt element))
2946 (defun bounding-chunk-p (x)
2947 (typep x 'bounding-chunk))
2949 (defun open-chunk-p (x)
2950 (typep x 'open-chunk))
2952 (defun close-chunk-p (x)
2953 (typep x 'close-chunk))
2955 ;; for first char:
2957 ;; When we detect that we are about to insert the first character we
2958 ;; just insert the :first-char pseudo open chunk. When while
2959 ;; continuing to insert chunks we find a close chunk we need to adjust
2960 ;; our style stack accordingly. It might also be possible that an open
2961 ;; chunk gets inserted. Also tricky: although there might be an open
2962 ;; chunk, that does not neccessarily mean that it is to be included in
2963 ;; the :first-char stuff. But generally we can say: we have flags:
2965 ;; first-char-pseudo-element-inserted-p
2966 ;; whether a first char pseudo element chunk was inserted.
2968 ;; first-char-p
2969 ;; whether still in first char situation.
2972 ;; We might however be better off by doing all this in a pre- or
2973 ;; post-analysis.
2975 (defun make-black-chunk* (char style)
2976 (cons-black-chunk
2977 :style style
2978 :data (map 'rod ;; war: (simple-array (unsigned-byte 16) (*))
2979 #'identity (list char))))
2981 ;;; first-letter pseudo elements
2983 ;; Proper support for first-letter is not that easy because it is said
2984 ;; that leading punctation characters should be included in what is
2985 ;; considered the first letter which means that the first letter is
2986 ;; not neccessarily a single letter. The further implication of this
2987 ;; is that there might be other elements going open or close between
2988 ;; individual letters of the 'first letter' span.
2990 ;; Since at least individual chunks must be nested properly we need to
2991 ;; insert half-chunks.
2993 ;; To illustrate that consider the following input:
2995 ;; "<B>we should check ...
2997 ;; The emited chunks now are:
2999 ;; <P:first-letter>"(/P:first-letter)<B>(P:first-letter)w</P:first-letter>
3001 ;; Where chunks in round parens are half chunks. This has some
3002 ;; implications:
3004 ;; a. When there is a first-letter element active, and other open or
3005 ;; close chunks are due we first insert a half-close chunk for the
3006 ;; first-letter, the other chunk and then reopen the first-letter
3007 ;; by inserting a half-open chunk.
3009 ;; b. This further implies that while in first-letter context the
3010 ;; first-letter pseudo element is always last recently opened one.
3011 ;; This is in the spirit of the CSS-2 specification.
3013 ;; c. Although half-chunks no nest properly, the corresponding full
3014 ;; chunks may no longer nest properly. That is the higher level
3015 ;; paragraph layout and in particular the line drawing code has to
3016 ;; be able to cope with that situation, which should case no
3017 ;; particular problems.
3019 ;; [although one might argue that the author force such situations,
3020 ;; not caring for them would seriously reduce the usefulness of
3021 ;; generated content like in:
3023 ;; Q:before { content: '"'; }
3025 ;; <Q>we should check for ...
3027 ;; ]
3032 ;; Now in case of a first-line element we need to actually process the
3033 ;; paragraph Once with the first-line element included and once with the
3034 ;; first line element not included. The disk-chunks of the first-line run
3035 ;; need to point to the appropriate stuff with their after link which just
3036 ;; is same as the after link of the not-first-line run.
3038 ;; For more precise: the after link of the nth disc-chunk in the first-line
3039 ;; run is the same as the after link of the nth disc-chunk in the
3040 ;; not-first-line run.
3042 ;; So our strategy now is to first do the not-first-line run and either keep
3043 ;; the disk-chunks. A second run is then done with the first-line element
3044 ;; included and the disk-chunks are then set up as outlined above.
3046 ;; What we want in the future:
3048 ;; a. only do two runs if the first-line element makes a difference all.
3049 ;; b. perhaps if this ever gets a performance problem use lazy evaluation.
3051 ;; Further note: Although it is not noted in the "specification" i simply
3052 ;; assume that first-line style should not apply to floating boxen. It also
3053 ;; probably should not apply to blocks (as they always (at least should)
3054 ;; leave the first line condition).
3056 ;; Generally the so called specification is not that clear about where
3057 ;; exactly first line style has to apply.
3060 ;; we can probably refactor this into:
3062 ;; handle-black
3063 ;; handle-white
3064 ;; handle-break
3065 ;; handle-open
3066 ;; handle-close
3067 ;; handle-suspend ;when the paragraph is to be suspended because of a block box
3068 ;; handle-resume ;resume the paragraph.
3072 (defmacro %handle-data/normal/none (first-line-applicable-p
3073 first-letter-applicable-p
3074 letter-spacing-applicable-p
3075 white-space)
3076 `(progn
3077 ,(AND LETTER-SPACING-APPLICABLE-P
3078 '(setf
3079 letter-spacing (if (eql :normal letter-spacing) 0 letter-spacing) ;### hmm this shouldn't be neccessary.
3080 word-spacing (+
3081 (if (eql :normal word-spacing) 0 word-spacing)
3082 letter-spacing)))
3083 (let ((blacki 0))
3084 (loop
3085 for c across data
3086 for i fixnum from 0 do
3087 (cond
3088 ,@(AND (EQL :PRE WHITE-SPACE)
3089 (list `((eql c #/U+000A)
3090 (let ((ocontext context))
3091 ,(OR LETTER-SPACING-APPLICABLE-P
3092 '(unless (= blacki i)
3093 (push (cons-black-chunk :style (car ss)
3094 :data (subseq data blacki i))
3095 res)))
3096 ,(AND (AND FIRST-LINE-APPLICABLE-P)
3097 `(when first-line-element
3098 ;; redo both style and context
3099 ;; first remove everything until first-line-element found
3100 ;; oops bug wrt to first-line ....
3101 (setf (values context ss) (frob context ss))
3102 (setf first-line-element nil)))
3103 (push (make-instance
3104 'disc-chunk
3105 :%before (mapcar #'(lambda (k)
3106 (make-half-close-chunk (bounding-chunk-pt k)
3107 (bounding-chunk-style k)))
3108 ocontext)
3109 :%after (mapcar #'(lambda (k)
3110 (make-half-open-chunk (bounding-chunk-pt k)
3111 (bounding-chunk-style k)))
3112 (reverse context))
3113 :%here nil
3114 :forcep t)
3115 res)
3116 (setf last-was-space-p t)
3117 (setf blacki (+ i 1))) )))
3119 ((white-space-rune-p c)
3120 (let ((ocontext context) (ncontext context) (nss ss))
3121 (unless ,(IF (EQL :PRE WHITE-SPACE) 'nil 'last-was-space-p)
3122 ,(OR LETTER-SPACING-APPLICABLE-P
3123 `(unless (= blacki i)
3124 (push (cons-black-chunk :style (car ss)
3125 :data (subseq data blacki i))
3126 res)))
3127 (setf last-was-space-p t)
3128 ;; what is that supposed to do?
3129 #+NIL
3130 ,(AND (AND FIRST-LINE-APPLICABLE-P
3131 (EQL WHITE-SPACE :NORMAL))
3132 `(when first-line-element
3133 ;; redo both style and context
3134 ;; first remove everything until first-line-element found
3135 ;; oops bug wrt to first-line ....
3136 (setf (values ncontext nss) (frob context ss))
3137 (setf first-line-element nil)))
3139 ,(ECASE WHITE-SPACE
3140 (:NORMAL
3141 `(let ((context context))
3142 ;; ### Now, this is too simplistic, this might as
3143 ;; well be the very last white space in this block,
3144 ;; in which case we do not want to have this disk
3145 ;; chunk.
3147 ;; When everything which follows is white or some
3148 ;; open/close stuff we should skip this. But: we
3149 ;; have to watch generated content.
3151 ;; The implementation approach needed is: keep this
3152 ;; disk chunk and spill it if something black comes
3153 ;; That is when last-was-space-p turns nil again.
3154 ;; ### white space convention?
3155 #+NIL
3156 (setf dangling-disc
3157 (make-instance
3158 'disc-chunk
3159 :%before (mapcar #'(lambda (k)
3160 (make-half-close-chunk (bounding-chunk-pt k)
3161 (bounding-chunk-style k)))
3162 ocontext)
3163 :%after (mapcar #'(lambda (k)
3164 (make-half-open-chunk (bounding-chunk-pt k)
3165 (bounding-chunk-style k)))
3166 (reverse ncontext))
3167 :%here ,(IF LETTER-SPACING-APPLICABLE-P
3168 `(if (eql word-spacing :normal)
3169 (list (make-black-chunk* #/U+0020 (car ss)))
3170 (list (make-black-chunk* #/U+0020 (car ss))
3171 (make-kern-chunk word-spacing)))
3172 `(list (make-black-chunk* #/U+0020 (car ss))))))
3173 #-NIL
3174 (push (make-instance
3175 'disc-chunk
3176 :%before (mapcar #'(lambda (k)
3177 (make-half-close-chunk (bounding-chunk-pt k)
3178 (bounding-chunk-style k)))
3179 ocontext)
3180 :%after (mapcar #'(lambda (k)
3181 (make-half-open-chunk (bounding-chunk-pt k)
3182 (bounding-chunk-style k)))
3183 (reverse ncontext))
3184 :%here ,(IF LETTER-SPACING-APPLICABLE-P
3185 `(if (eql word-spacing :normal)
3186 (list (make-black-chunk* #/U+0020 (car ss)))
3187 (list (make-black-chunk* #/U+0020 (car ss))
3188 (make-kern-chunk word-spacing)))
3189 `(list (make-black-chunk* #/U+0020 (car ss)))))
3190 res)))
3191 ((:PRE)
3192 `(progn
3193 ,(IF LETTER-SPACING-APPLICABLE-P
3194 `(if (eql word-spacing :normal)
3195 (push (make-black-chunk* #/U+0020 (car ss)) res)
3196 (progn
3197 (push (make-black-chunk* #/U+0020 (car ss)) res)
3198 (push (make-kern-chunk word-spacing) res)))
3199 `(push (make-black-chunk* #/U+0020 (car ss)) res) )
3200 (setf blacki (+ i 1))))
3201 ((:NOWRAP)
3202 `(progn
3203 ,(IF LETTER-SPACING-APPLICABLE-P
3204 `(if (eql word-spacing :normal)
3205 (push (make-black-chunk* #/U+0020 (car ss)) res)
3206 (progn
3207 (push (make-black-chunk* #/U+0020 (car ss)) res)
3208 (push (make-kern-chunk word-spacing) res)))
3209 `(push (make-black-chunk* #/U+0020 (car ss)) res) )))))))
3212 ,(AND LETTER-SPACING-APPLICABLE-P
3213 `(unless last-was-space-p
3214 (push (make-kern-chunk (max letter-spacing
3215 (- (chunk-width (make-black-chunk* c (car ss))))))
3216 res)))
3217 (when last-was-space-p
3218 ;; ### spill the disk before or after the kern? Does it matter?
3219 (when dangling-disc
3220 (push dangling-disc res)
3221 (setf dangling-disc nil))
3222 (setf blacki i)
3223 (setf last-was-space-p nil))
3224 ,(AND FIRST-LETTER-APPLICABLE-P
3225 `(when (and first-letter-p (not first-letter-element))
3226 (first-letter-start)))
3227 ,(AND LETTER-SPACING-APPLICABLE-P
3228 `(push (make-black-chunk* c (car ss)) res))
3229 ,(AND FIRST-LETTER-APPLICABLE-P
3230 `(when first-letter-element
3231 ,(OR LETTER-SPACING-APPLICABLE-P
3232 `(push (make-black-chunk* c (car ss)) res))
3233 (unless (punctation-character-p c)
3234 (when first-letter-element
3235 (first-letter-end)
3236 (setf first-letter-element nil)
3237 (setf first-letter-p nil)))
3238 (setf blacki (+ i 1))))) ) )
3240 (unless last-was-space-p
3241 ,(OR LETTER-SPACING-APPLICABLE-P
3242 `(unless (= blacki (length data))
3243 (push (cons-black-chunk
3244 :style (car ss)
3245 :data (subseq data blacki))
3246 res))))) ))
3248 ;; Text transform is now handled before anything else to keep code size
3249 ;; reasonable.
3251 (defun transform-rod (rod transformation &optional (previous-rune #/.))
3252 (ecase transformation
3253 (:none
3254 rod)
3255 (:uppercase
3256 (map 'rod #'rune-upcase rod))
3257 (:lowercase
3258 (map 'rod #'rune-downcase rod))
3259 (:capitalize
3260 ;; more complicated
3261 (let ((res (make-rod (length rod))))
3262 (loop for c = previous-rune then d
3263 for d across rod
3264 for i from 0 do
3265 (setf (rune res i)
3266 (cond ((runes::rune-upper-case-letter-p c) d)
3267 ((runes::rune-lower-case-letter-p c) (rune-downcase d))
3268 (t (rune-upcase d)))))
3269 res))))
3271 (defun popo-para (q ss block-element block-style
3272 &key;; the kind of run we do
3273 ;; :not-first-line -- first pass
3274 ;; :first-line -- second pass first-line to be applied
3275 ;; :invariant -- first line not included and it makes no difference
3276 (run :not-first-line)
3278 ;; ### we disabled apply-first-line-p since it breaks in case of
3279 ;; the para-box entirely containing out of floating boxen ...
3281 ;; ### in fact we introduce dangling white space, if the element happens to
3282 ;; have some.
3284 ;; ### also: we sometimes wind up with empty black chunks. Is that intentional?
3286 ;; ### and: first line is not always going close? Does it matter?
3288 ;; ### and: this badly needs refactoring.
3290 ;; can't we still go with dangling-white somehow?
3291 ;; ss is current style stack (at entry)
3292 ;;(assert (eq (slot-value block-style 'css::%element) block-element))
3293 (setf run :invariant)
3295 ;; ### the no break at all path lacks a close element.
3297 (let* (res
3298 nres context
3299 (last-was-space-p t)
3300 (block-style (car ss))
3301 (white-space (cooked-style-white-space block-style))
3302 (first-letter-element nil)
3303 (first-letter-p t) ;still in first-char condition?
3304 ;; the possible open first-line-element
3305 (first-line-element nil)
3306 (first-line-style nil)
3308 (first-line-chunk nil)
3309 (para-start-q q)
3310 (para-start-ss ss)
3311 (previous-rune #/.)
3312 (dangling-disc nil) ;dangling discretionary chunk in case last-was-space-p is t,
3313 ; to be spilled just before when last-was-space-p turns nil again.
3314 ; ### see if their spillment is correct!
3316 (push '(:para-start) res)
3318 (labels
3319 ((frob (context style-stack)
3320 (cond ((eq (bounding-chunk-pt (car context)) first-line-element)
3321 (values (cdr context) (cdr style-stack)))
3323 (multiple-value-bind (p q)
3324 (frob (cdr context) (cdr style-stack))
3325 (let ((style
3326 (my-setup-style (bounding-chunk-pt (car context))
3327 (car q)
3328 block-style)))
3329 (values
3330 (cons (make-instance 'bounding-chunk
3331 :pt (bounding-chunk-pt (car context))
3332 :style style)
3334 (cons style q)))))))
3336 (add-content (content)
3337 ;; ### CSS2 is silent about the precise white space convention to be used.
3338 ;; ### handle all possible content variants
3339 (dolist (x content)
3340 (cond ((consp x)
3341 (case (car x)
3342 ((:string)
3343 (handle-data (string-rod (cadr x))))
3344 ((:url)
3346 ((:counter)
3348 ((:counters)
3350 ((:attr)
3351 )))))
3354 (first-letter-start ()
3355 ;; ### what to do if the first letter ends up floating?
3356 ;; we can collect the chunks as usual but after the
3357 ;; element ended we need to stuff them into a floating
3358 ;; box.
3359 (let* ((element (make-instance 'first-letter-pseudo-element :proxee block-element))
3360 (style (my-setup-style element (car ss) block-style)))
3361 (setf first-letter-element element)
3362 (handle-open first-letter-element style) ))
3364 (first-letter-end ()
3366 (handle-close first-letter-element))
3368 ;; Some optimized variants of handle-data:
3370 (handle-data (data)
3371 (unless (eql (cooked-style-text-transform (car ss)) :normal)
3372 (setf data (transform-rod data (cooked-style-text-transform (car ss))
3373 previous-rune)))
3374 (unless (zerop (length data))
3375 (setf previous-rune (aref data (1- (length data)))))
3377 ;; ### we need another whitespace convention for generated content
3378 (ecase white-space
3379 (:normal
3380 (let ((word-spacing (cooked-style-word-spacing (car ss)))
3381 (letter-spacing (cooked-style-letter-spacing (car ss))))
3382 (if (eql letter-spacing 0) (setf letter-spacing :normal)) ;###
3383 (if (and (eql word-spacing :normal)
3384 (eql letter-spacing :normal))
3385 (if first-line-element
3386 (if first-letter-p
3387 (%handle-data/normal/none t t nil :normal)
3388 (%handle-data/normal/none t nil nil :normal))
3389 (%handle-data/normal/none nil nil nil :normal))
3390 (if first-line-element
3391 (if first-letter-p
3392 (%handle-data/normal/none t t t :normal)
3393 (%handle-data/normal/none t nil t :normal))
3394 (%handle-data/normal/none nil nil t :normal)))))
3395 (:nowrap
3396 (let ((word-spacing (cooked-style-word-spacing (car ss)))
3397 (letter-spacing (cooked-style-letter-spacing (car ss))))
3398 (when (eql letter-spacing :normal) (setf letter-spacing 0))
3399 (%handle-data/normal/none t t t :nowrap)
3400 #+NIL
3401 (if (and (eql word-spacing :normal)
3402 (eql letter-spacing :normal))
3403 (if first-line-element
3404 (if first-letter-p
3405 (%handle-data/normal/none t t nil :nowrap)
3406 (%handle-data/normal/none t nil nil :nowrap))
3407 (%handle-data/normal/none nil nil nil :nowrap))
3408 (if first-line-element
3409 (if first-letter-p
3410 (%handle-data/normal/none t t t :nowrap)
3411 (%handle-data/normal/none t nil t :nowrap))
3412 (%handle-data/normal/none nil nil t :nowrap)))))
3413 (:pre
3414 (let ((word-spacing (cooked-style-word-spacing (car ss)))
3415 (letter-spacing (cooked-style-letter-spacing (car ss))))
3416 (when (eql letter-spacing :normal) (setf letter-spacing 0))
3417 (%handle-data/normal/none t t t :pre)
3418 #+NIL
3419 (if (and (eql word-spacing :normal)
3420 (eql letter-spacing :normal))
3421 (if first-line-element
3422 (if first-letter-p
3423 (%handle-data/normal/none t t nil :pre)
3424 (%handle-data/normal/none t nil nil :pre))
3425 (%handle-data/normal/none nil nil nil :pre))
3426 (if first-line-element
3427 (if first-letter-p
3428 (%handle-data/normal/none t t t :pre)
3429 (%handle-data/normal/none t nil t :pre))
3430 (%handle-data/normal/none nil nil t :pre)))))))
3432 (handle-generated-content (class element)
3433 ;; ### we need to restrict and watch the possible display values.
3434 (let* ((pseudo-element (make-instance class :proxee element))
3435 (pseudo-style (my-setup-style pseudo-element (car ss) block-style))
3436 (pseudo-content (cooked-style-content pseudo-style)))
3437 (unless (null pseudo-content)
3438 (handle-open pseudo-element pseudo-style)
3439 (add-content pseudo-content)
3440 (handle-close pseudo-element) )))
3442 (handle-open (element &optional (style (my-setup-style element (car ss) block-style)))
3443 (cond ((and first-letter-element (not (eq element first-letter-element)))
3444 ;; ### the style might not be correct!
3445 (assert (eq (bounding-chunk-pt (car context)) first-letter-element))
3446 (let ((fl.style (bounding-chunk-style (car context))))
3447 ;; half-close the first-letter-element
3448 (push (make-half-close-chunk
3449 first-letter-element
3450 fl.style) res)
3451 (pop context)
3452 (pop ss)
3453 (setf style (my-setup-style element (car ss) block-style))
3454 ;; insert our open tag
3455 (let ((chunk (make-open-chunk element style)))
3456 (push chunk res)
3457 (push chunk context))
3458 (push style ss)
3459 ;; half-open the flchunk again
3460 (let* ((s (my-setup-style first-letter-element (car ss) block-style))
3461 (c (make-half-open-chunk first-letter-element s)))
3462 (push c res)
3463 (push c context)
3464 (push s ss))))
3466 (push style ss)
3467 (let ((chunk (make-open-chunk element (car ss))))
3468 (push chunk res)
3469 (push chunk context) ))))
3471 (handle-close (element)
3472 (cond ((and first-letter-element (not (eq element first-letter-element)))
3473 (assert (eq (bounding-chunk-pt (car context)) first-letter-element))
3474 (let ((fl.style (bounding-chunk-style (car context))))
3475 ;; half-close the first-letter-element
3476 (push (make-half-close-chunk
3477 first-letter-element
3478 fl.style) res)
3479 (pop context)
3480 (pop ss)
3481 ;; ### reset-up style?
3482 (push (make-close-chunk element (car ss)) res)
3483 (pop context)
3484 (pop ss)
3485 ;; half-open the flchunk again
3486 (let* ((s (my-setup-style first-letter-element (car ss) block-style))
3487 (c (make-half-open-chunk first-letter-element s)))
3488 (push c res)
3489 (push c context)
3490 (push s ss))))
3492 (when (null ss)
3493 (error "HANDLE-CLOSE [~A]on an empty style stack?"
3494 (chunk-debug-name (make-instance 'close-chunk :pt element))))
3495 (push (make-close-chunk element (car ss)) res)
3496 (pop context)
3497 (pop ss))))
3499 (clean-para (&aux non-halfp)
3500 ;; ### is this entirely right?
3501 (do ((q res (cdr q)))
3502 ((equal (car q) '(:para-start))
3503 (cond ((not non-halfp)
3504 (setf res (cdr q))
3505 nil)
3507 t)))
3508 (when (and (not (and (bounding-chunk-p (car q))
3509 (bounding-chunk-halfp (car q))))
3510 (not (and (bounding-chunk-p (car q))
3511 (typep (bounding-chunk-pt (car q)) 'pseudo-element))))
3512 (setf non-halfp t))))
3514 (gather-para ()
3515 (let ((items))
3516 (do ((x (pop res) (pop res)))
3517 ((equal x '(:para-start)))
3518 (push x items))
3519 (make-para-box :items items)))
3521 (stuff-para ()
3522 (cond ((clean-para)
3523 ;; and here comes the magic:
3524 (ecase run
3525 (:not-first-line
3526 ;; we need to call ourself again
3527 (let ((my-para-box (gather-para))
3528 (mirror-para-box
3529 (popo-para para-start-q para-start-ss block-element block-style
3530 :run :first-line)))
3531 ;; now simultaneously traverse both our and the mirror
3532 ;; universe updating disk-links as we go
3533 ;; ### what is with possible floating chunks?
3534 ;; we definitly want them only in non-first-line style
3535 ;; and skip building them in non-first-line style.
3536 (let ((f (para-box-items mirror-para-box))
3537 (o (para-box-items my-para-box)))
3538 (loop
3539 ;; walk both until we find either a disc-chunk or nil
3540 (do () ((or (null f) (disc-chunk-p (car f)))) (setf f (cdr f)))
3541 (do () ((or (null o) (disc-chunk-p (car o)))) (setf o (cdr o)))
3542 (assert (or (and (null f) (null o))
3543 (and f o)))
3544 (when (null f)
3545 (return))
3546 (setf (disc-chunk-%here (car f))
3547 (append (disc-chunk-here (car f)) (cdr f)))
3548 (setf (disc-chunk-%after (car f))
3549 (append (disc-chunk-after (car o)) (cdr o)))
3550 (psetf (cdr f) nil
3551 f (cdr f))
3552 (setf o (cdr o))))
3553 (push mirror-para-box res)
3554 (setf run :invariant) ;the horror is over
3556 (:first-line
3557 ;; the first line run, just return what we found
3558 (when first-line-element (handle-close first-line-element))
3559 ;; grrf this is still not 100% correct ;(
3560 (let ((z (gather-para)))
3561 '(format *trace-output* "~&;; first line run ended, we have:~%;; ~S."
3563 (return-from popo-para z) ))
3564 (:invariant
3565 ;; nothing special
3566 (push (gather-para) res)) ))
3568 ;; we decided to skip this para, so be nice and reset para-start-*
3569 (setf para-start-q q
3570 para-start-ss ss) )) )
3572 (handle-suspend ()
3573 "This is called whenever the paragraph has to be 'suspended' because another
3574 block box came its way."
3575 ;; close the context
3576 (dolist (k context)
3577 (push (make-half-close-chunk (bounding-chunk-pt k)
3578 (bounding-chunk-style k))
3579 res))
3580 ;; finish the current paragraph
3581 (stuff-para) )
3583 (handle-resume ()
3584 "This is called when a paragraph suspended because of a block-box is to be resumed again."
3585 ;; re-open the paragraph and re-open the context
3586 (push '(:para-start) res)
3587 (setf last-was-space-p t)
3588 (dolist (k (reverse context))
3589 (push (make-half-open-chunk (bounding-chunk-pt k)
3590 (bounding-chunk-style k))
3591 res)) ) )
3593 (cond ((eql run :first-line)
3594 (setf first-line-element
3595 (make-instance 'first-line-pseudo-element
3596 :proxee block-element))
3597 (setf first-line-style (my-setup-style first-line-element (car ss) block-style))
3599 (setf first-line-chunk
3600 (make-instance
3601 'bounding-chunk
3602 :pt first-line-element
3603 :style first-line-style))
3604 (handle-open first-line-element)
3607 (let ()
3608 (tagbody
3610 (when (null q)
3611 ;; the paragraph must have ended.
3612 (stuff-para)
3613 (return-from popo-para
3614 (values (reverse res) q)))
3616 (let* ((kind (caar q))
3617 (data (cadar q))
3618 (s (and (member kind '(:open :close))
3619 (if (eq data block-element)
3620 block-style
3621 ;; bang! this failes when this is the close
3622 ;; element of the block around this para!
3623 (my-setup-style data (car ss) block-style)))) )
3625 (cond
3626 ;;; regular block stuff.
3627 ((and (eq kind :open)
3628 (cooked-style-block-element-p s))
3629 ;; This element can be on of:
3630 ;; . a regular block
3631 ;; . a floating box
3632 ;; . a replaced element
3633 ;; . a absolutely positioned regular block
3634 ;; . a absolutely positioned replaced block
3636 ;; When this is a replaced element we want can do:
3637 ;; construct a block with a para whose items then are
3638 ;; <IMG> <RO/> </IMG>
3639 ;; so that the regular other stuff applies.
3643 ;; ### now this might as well be floating box ... in
3644 ;; which case we should just collect the block box as
3645 ;; usual but stuffing it into a floating-chunk.
3646 ;; ### also: keep proper track of the 'containing block'
3647 ;; for style (that is width and stuff).
3649 ;; ### and then we need the very same logic for floating
3650 ;; elements which are generated by possible block
3651 ;; content [above in popo-block].
3654 (let ((float (cooked-style-float s))
3655 (replaced-element
3656 (replaced-element-p *document* *device* data))
3657 content)
3659 #+NIL
3660 (progn
3661 ;; ### This variant is now quite right wrt to 'q'
3663 ;; find the content, depends on replaced/non-replaced
3664 (setf content (cond ((null replaced-element)
3665 ;; --- non-replaced
3666 (setf (values nres q) (popo-block q ss block-style)) ;###
3667 nres)
3669 ;; --- replaced
3670 ;; skip the whole rest
3671 (setf q (cdr (member-if (lambda (y) (eq (second y) data)) (cdr q))))
3672 (make-block-box
3673 :content
3674 (list
3675 (make-para-box
3676 :items
3677 (list (cons-replaced-object-chunk
3678 :element data
3679 :object replaced-element))))
3680 :style s
3681 :element data))))
3682 ;; spill the content, depends on floating/non-floating
3683 (cond ((eql float :none)
3684 (handle-suspend)
3685 (push content res)
3686 (handle-resume))
3688 (push (make-instance 'floating-chunk
3689 :style s
3690 :content content)
3691 res))))
3693 #-NIL
3694 (cond
3696 ;; ---- non-floating, non-replaced block
3697 ((and (eql float :none) (null replaced-element))
3698 (handle-suspend)
3699 ;; gather the block and push it onto our bag
3700 ;; note that the containing block does not change
3701 (setf (values nres q) (popo-block q ss block-style))
3702 (push nres res)
3703 (handle-resume))
3705 ;; ---- non-floating, replaced block
3706 ((and (eql float :none) (not (null replaced-element)))
3707 (handle-suspend)
3708 (push (make-block-box :content
3709 (list
3710 (make-para-box :items
3711 (list (cons-replaced-object-chunk
3712 :element data
3713 :object replaced-element))))
3714 :style s
3715 :element data)
3716 res)
3717 ;; skip the whole rest
3718 (setf q (cdr (member-if (lambda (y) (eq (second y) data)) (cdr q))))
3719 (handle-resume))
3721 ;; ---- floating
3722 ((not (eql float :none))
3723 (push (make-instance 'floating-chunk
3724 :style s
3725 :content
3726 (cond ((null replaced-element)
3727 ;; --- non-replaced
3728 (setf (values nres q) (popo-block q ss block-style)) ;###
3729 nres)
3731 ;; --- replaced
3732 ;; skip the whole rest
3733 (setf q (cdr (member-if (lambda (y) (eq (second y) data)) (cdr q))))
3734 (make-block-box
3735 :content
3736 (list
3737 (make-para-box
3738 :items
3739 (list (cons-replaced-object-chunk
3740 :element data
3741 :object replaced-element))))
3742 :style s
3743 :element data))))
3744 res))
3746 ;; ---- floating, non-replaced block
3747 ((and (not (eql float :none))
3748 (null replaced-element))
3749 ;; this is a floating box, it will not in
3750 ;; anyway end the paragraph, we rather collect
3751 ;; it into a floating-chunk.
3752 ;; ### what is the containing block here?
3753 (setf (values nres q) (popo-block q ss block-style)) ;###
3754 (push (make-instance 'floating-chunk
3755 :style s
3756 :content nres)
3757 res) )
3759 ;; --- floating, replaced block
3760 ((and (not (eql float :none))
3761 (not (null replaced-element)))
3762 ;; skip the whole rest
3763 (setf q (cdr (member-if (lambda (y) (eq (second y) data)) (cdr q))))
3764 (push (make-instance
3765 'floating-chunk
3766 :style s
3767 :content (make-block-box
3768 :content
3769 (list
3770 (make-para-box
3771 :items
3772 (list (cons-replaced-object-chunk
3773 :element data
3774 :object replaced-element))))
3775 :style s
3776 :element data))
3777 res) ) ))
3779 (go L.0))
3781 ((and (eq kind :close)
3782 (cooked-style-block-element-p s))
3783 ;; the paragraph must have ended.
3784 (stuff-para)
3785 (return-from popo-para
3786 (values (reverse res) q)))
3788 ((and (eq kind :open)
3789 (eq (cooked-style-display s) :none))
3790 (setf q (member-if (lambda (y) (eq (second y) data)) (cdr q))) )
3792 ((and (eq kind :open)
3793 (member (cooked-style-display s) '(:marker :inline)))
3794 (when dangling-disc
3795 (push dangling-disc res)
3796 (setf dangling-disc nil))
3797 ;; ### hmm ...
3798 ;; Note: A before element can have a display of either :inline or :none
3799 ;; So we wind up with either
3800 ;; open-chunk(before) context close-chunk(before)
3801 ;; or nothing.
3802 (assert s)
3803 (handle-open data s)
3804 (unless (typep data 'pseudo-element)
3805 (handle-generated-content 'before-pseudo-element data))
3806 (when (typep data 'pseudo-element)
3807 (add-content (cooked-style-content s)))
3809 (multiple-value-bind (re re-map)
3810 (if nil ;;(eql :img (element-gi data))
3811 (make-instance 'lazy-image)
3812 (replaced-element-p *document* *device* data))
3813 (when re
3814 (when last-was-space-p
3815 (when dangling-disc
3816 (push dangling-disc res)
3817 (setf dangling-disc nil))
3818 (setf last-was-space-p nil))
3820 (when first-letter-element
3821 (first-letter-end)
3822 (setf first-letter-element nil)
3823 (setf first-letter-p nil))
3825 (push (make-instance 'replaced-object-chunk
3826 :element data
3827 :object re)
3828 res))) )
3830 ((and (eq kind :close)
3831 (member (cooked-style-display s) '(:inline :marker)))
3832 ;; Note: An after element can have a display of either :inline or :none
3833 (unless (typep data 'pseudo-element)
3834 (handle-generated-content 'after-pseudo-element data))
3835 (handle-close data))
3837 ((eq kind :data)
3838 (handle-data data))
3841 (error "Unexpected item: ~S." (car q))) ))
3843 L.continue
3844 (setf q (cdr q))
3845 (go L.0)))) ))
3850 ;;;;
3852 (defun punctation-character-p (char)
3853 (member char
3854 '(#x0021 ;EXCLAMATION MARK;Po;0;ON;;;;;N;;;;;
3855 #x0022 ;QUOTATION MARK;Po;0;ON;;;;;N;;;;;
3856 #x0023 ;NUMBER SIGN;Po;0;ET;;;;;N;;;;;
3857 #x0025 ;PERCENT SIGN;Po;0;ET;;;;;N;;;;;
3858 #x0026 ;AMPERSAND;Po;0;ON;;;;;N;;;;;
3859 #x0027 ;APOSTROPHE;Po;0;ON;;;;;N;APOSTROPHE-QUOTE;;;;
3860 #x002A ;ASTERISK;Po;0;ON;;;;;N;;;;;
3861 #x002C ;COMMA;Po;0;CS;;;;;N;;;;;
3862 #x002E ;FULL STOP;Po;0;CS;;;;;N;PERIOD;;;;
3863 #x002F ;SOLIDUS;Po;0;ES;;;;;N;SLASH;;;;
3864 #x003A ;COLON;Po;0;CS;;;;;N;;;;;
3865 #x003B ;SEMICOLON;Po;0;ON;;;;;N;;;;;
3866 #x003F ;QUESTION MARK;Po;0;ON;;;;;N;;;;;
3867 #x0040 ;COMMERCIAL AT;Po;0;ON;;;;;N;;;;;
3868 #x005C ;REVERSE SOLIDUS;Po;0;ON;;;;;N;BACKSLASH;;;;
3869 #x00A1 ;INVERTED EXCLAMATION MARK;Po;0;ON;;;;;N;;;;;
3870 #x00B7 ;MIDDLE DOT;Po;0;ON;;;;;N;;;;;
3871 #x00BF ;INVERTED QUESTION MARK;Po;0;ON;;;;;N;;;;;
3872 #x0374 ;GREEK NUMERAL SIGN;Po;0;L;02B9;;;;N;GREEK UPPER NUMERAL SIGN;Dexia keraia;;;
3873 #x0375 ;GREEK LOWER NUMERAL SIGN;Po;0;L;;;;;N;;Aristeri keraia;;;
3874 #x037E ;GREEK QUESTION MARK;Po;0;ON;003B;;;;N;;Erotimatiko;;;
3875 #x0387 ;GREEK ANO TELEIA;Po;0;ON;00B7;;;;N;;;;;
3876 #x055A ;ARMENIAN APOSTROPHE;Po;0;L;;;;;N;ARMENIAN MODIFIER LETTER RIGHT HALF RING;;;;
3877 #x055B ;ARMENIAN EMPHASIS MARK;Po;0;L;;;;;N;;;;;
3878 #x055C ;ARMENIAN EXCLAMATION MARK;Po;0;L;;;;;N;;;;;
3879 #x055D ;ARMENIAN COMMA;Po;0;L;;;;;N;;;;;
3880 #x055E ;ARMENIAN QUESTION MARK;Po;0;L;;;;;N;;;;;
3881 #x055F ;ARMENIAN ABBREVIATION MARK;Po;0;L;;;;;N;;;;;
3882 #x0589 ;ARMENIAN FULL STOP;Po;0;L;;;;;N;ARMENIAN PERIOD;;;;
3883 #x05BE ;HEBREW PUNCTUATION MAQAF;Po;0;R;;;;;N;;;;;
3884 #x05C0 ;HEBREW PUNCTUATION PASEQ;Po;0;R;;;;;N;HEBREW POINT PASEQ;;;;
3885 #x05C3 ;HEBREW PUNCTUATION SOF PASUQ;Po;0;R;;;;;N;;;;;
3886 #x05F3 ;HEBREW PUNCTUATION GERESH;Po;0;R;;;;;N;;;;;
3887 #x05F4 ;HEBREW PUNCTUATION GERSHAYIM;Po;0;R;;;;;N;;;;;
3888 #x060C ;ARABIC COMMA;Po;0;CS;;;;;N;;;;;
3889 #x061B ;ARABIC SEMICOLON;Po;0;R;;;;;N;;;;;
3890 #x061F ;ARABIC QUESTION MARK;Po;0;R;;;;;N;;;;;
3891 #x066A ;ARABIC PERCENT SIGN;Po;0;ET;;;;;N;;;;;
3892 #x066B ;ARABIC DECIMAL SEPARATOR;Po;0;AN;;;;;N;;;;;
3893 #x066C ;ARABIC THOUSANDS SEPARATOR;Po;0;AN;;;;;N;;;;;
3894 #x066D ;ARABIC FIVE POINTED STAR;Po;0;R;;;;;N;;;;;
3895 #x06D4 ;ARABIC FULL STOP;Po;0;R;;;;;N;ARABIC PERIOD;;;;
3896 #x0964 ;DEVANAGARI DANDA;Po;0;L;;;;;N;;;;;
3897 #x0965 ;DEVANAGARI DOUBLE DANDA;Po;0;L;;;;;N;;;;;
3898 #x0970 ;DEVANAGARI ABBREVIATION SIGN;Po;0;L;;;;;N;;;;;
3899 #x0E5A ;THAI CHARACTER ANGKHANKHU;Po;0;L;;;;;N;THAI ANGKHANKHU;;;;
3900 #x0E5B ;THAI CHARACTER KHOMUT;Po;0;L;;;;;N;THAI KHOMUT;;;;
3901 #x0F04 ;TIBETAN MARK INITIAL YIG MGO MDUN MA;Po;0;L;;;;;N;;yik go dun ma;;;
3902 #x0F05 ;TIBETAN MARK CLOSING YIG MGO SGAB MA;Po;0;L;;;;;N;;yik go kab ma;;;
3903 #x0F06 ;TIBETAN MARK CARET YIG MGO PHUR SHAD MA;Po;0;L;;;;;N;;yik go pur shey ma;;;
3904 #x0F07 ;TIBETAN MARK YIG MGO TSHEG SHAD MA;Po;0;L;;;;;N;;yik go tsek shey ma;;;
3905 #x0F08 ;TIBETAN MARK SBRUL SHAD;Po;0;L;;;;;N;;drul shey;;;
3906 #x0F09 ;TIBETAN MARK BSKUR YIG MGO;Po;0;L;;;;;N;;kur yik go;;;
3907 #x0F0A ;TIBETAN MARK BKA- SHOG YIG MGO;Po;0;L;;;;;N;;ka sho yik go;;;
3908 #x0F0B ;TIBETAN MARK INTERSYLLABIC TSHEG;Po;0;L;;;;;N;;tsek;;;
3909 #x0F0C ;TIBETAN MARK DELIMITER TSHEG BSTAR;Po;0;L;;;;;N;;tsek tar;;;
3910 #x0F0D ;TIBETAN MARK SHAD;Po;0;L;;;;;N;;shey;;;
3911 #x0F0E ;TIBETAN MARK NYIS SHAD;Po;0;L;;;;;N;;nyi shey;;;
3912 #x0F0F ;TIBETAN MARK TSHEG SHAD;Po;0;L;;;;;N;;tsek shey;;;
3913 #x0F10 ;TIBETAN MARK NYIS TSHEG SHAD;Po;0;L;;;;;N;;nyi tsek shey;;;
3914 #x0F11 ;TIBETAN MARK RIN CHEN SPUNGS SHAD;Po;0;L;;;;;N;;rinchen pung shey;;;
3915 #x0F12 ;TIBETAN MARK RGYA GRAM SHAD;Po;0;L;;;;;N;;gya tram shey;;;
3916 #x0F85 ;TIBETAN MARK PALUTA;Po;0;L;;;;;N;;;;;
3917 #x10FB ;GEORGIAN PARAGRAPH SEPARATOR;Po;0;L;;;;;N;;;;;
3918 #x2016 ;DOUBLE VERTICAL LINE;Po;0;ON;;;;;N;DOUBLE VERTICAL BAR;;;;
3919 #x2017 ;DOUBLE LOW LINE;Po;0;ON;<compat> 0020 0333;;;;N;SPACING DOUBLE UNDERSCORE;;;;
3920 #x2020 ;DAGGER;Po;0;ON;;;;;N;;;;;
3921 #x2021 ;DOUBLE DAGGER;Po;0;ON;;;;;N;;;;;
3922 #x2022 ;BULLET;Po;0;ON;;;;;N;;;;;
3923 #x2023 ;TRIANGULAR BULLET;Po;0;ON;;;;;N;;;;;
3924 #x2024 ;ONE DOT LEADER;Po;0;ON;<compat> 002E;;;;N;;;;;
3925 #x2025 ;TWO DOT LEADER;Po;0;ON;<compat> 002E 002E;;;;N;;;;;
3926 #x2026 ;HORIZONTAL ELLIPSIS;Po;0;ON;<compat> 002E 002E 002E;;;;N;;;;;
3927 #x2027 ;HYPHENATION POINT;Po;0;ON;;;;;N;;;;;
3928 #x2030 ;PER MILLE SIGN;Po;0;ET;;;;;N;;;;;
3929 #x2031 ;PER TEN THOUSAND SIGN;Po;0;ET;;;;;N;;;;;
3930 #x2032 ;PRIME;Po;0;ET;;;;;N;;;;;
3931 #x2033 ;DOUBLE PRIME;Po;0;ET;<compat> 2032 2032;;;;N;;;;;
3932 #x2034 ;TRIPLE PRIME;Po;0;ET;<compat> 2032 2032 2032;;;;N;;;;;
3933 #x2035 ;REVERSED PRIME;Po;0;ON;;;;;N;;;;;
3934 #x2036 ;REVERSED DOUBLE PRIME;Po;0;ON;<compat> 2035 2035;;;;N;;;;;
3935 #x2037 ;REVERSED TRIPLE PRIME;Po;0;ON;<compat> 2035 2035 2035;;;;N;;;;;
3936 #x2038 ;CARET;Po;0;ON;;;;;N;;;;;
3937 #x203B ;REFERENCE MARK;Po;0;ON;;;;;N;;;;;
3938 #x203C ;DOUBLE EXCLAMATION MARK;Po;0;ON;<compat> 0021 0021;;;;N;;;;;
3939 #x203D ;INTERROBANG;Po;0;ON;;;;;N;;;;;
3940 #x203E ;OVERLINE;Po;0;ON;<compat> 0020 0305;;;;N;SPACING OVERSCORE;;;;
3941 #x2041 ;CARET INSERTION POINT;Po;0;ON;;;;;N;;;;;
3942 #x2042 ;ASTERISM;Po;0;ON;;;;;N;;;;;
3943 #x2043 ;HYPHEN BULLET;Po;0;ON;;;;;N;;;;;
3944 #x3001 ;IDEOGRAPHIC COMMA;Po;0;ON;;;;;N;;;;;
3945 #x3002 ;IDEOGRAPHIC FULL STOP;Po;0;ON;;;;;N;IDEOGRAPHIC PERIOD;;;;
3946 #x3003 ;DITTO MARK;Po;0;ON;;;;;N;;;;;
3947 #xFE30 ;PRESENTATION FORM FOR VERTICAL TWO DOT LEADER;Po;0;ON;<vertical> 2025;;;;N;GLYPH FOR VERTICAL TWO DOT LEADER;;;;
3948 #xFE49 ;DASHED OVERLINE;Po;0;ON;<compat> 203E;;;;N;SPACING DASHED OVERSCORE;;;;
3949 #xFE4A ;CENTRELINE OVERLINE;Po;0;ON;<compat> 203E;;;;N;SPACING CENTERLINE OVERSCORE;;;;
3950 #xFE4B ;WAVY OVERLINE;Po;0;ON;<compat> 203E;;;;N;SPACING WAVY OVERSCORE;;;;
3951 #xFE4C ;DOUBLE WAVY OVERLINE;Po;0;ON;<compat> 203E;;;;N;SPACING DOUBLE WAVY OVERSCORE;;;;
3952 #xFE50 ;SMALL COMMA;Po;0;CS;<small> 002C;;;;N;;;;;
3953 #xFE51 ;SMALL IDEOGRAPHIC COMMA;Po;0;ON;<small> 3001;;;;N;;;;;
3954 #xFE52 ;SMALL FULL STOP;Po;0;CS;<small> 002E;;;;N;SMALL PERIOD;;;;
3955 #xFE54 ;SMALL SEMICOLON;Po;0;ON;<small> 003B;;;;N;;;;;
3956 #xFE55 ;SMALL COLON;Po;0;CS;<small> 003A;;;;N;;;;;
3957 #xFE56 ;SMALL QUESTION MARK;Po;0;ON;<small> 003F;;;;N;;;;;
3958 #xFE57 ;SMALL EXCLAMATION MARK;Po;0;ON;<small> 0021;;;;N;;;;;
3959 #xFE5F ;SMALL NUMBER SIGN;Po;0;ET;<small> 0023;;;;N;;;;;
3960 #xFE60 ;SMALL AMPERSAND;Po;0;ON;<small> 0026;;;;N;;;;;
3961 #xFE61 ;SMALL ASTERISK;Po;0;ON;<small> 002A;;;;N;;;;;
3962 #xFE68 ;SMALL REVERSE SOLIDUS;Po;0;ON;<small> 005C;;;;N;SMALL BACKSLASH;;;;
3963 #xFE6A ;SMALL PERCENT SIGN;Po;0;ET;<small> 0025;;;;N;;;;;
3964 #xFE6B ;SMALL COMMERCIAL AT;Po;0;ON;<small> 0040;;;;N;;;;;
3965 #xFF01 ;FULLWIDTH EXCLAMATION MARK;Po;0;ON;<wide> 0021;;;;N;;;;;
3966 #xFF02 ;FULLWIDTH QUOTATION MARK;Po;0;ON;<wide> 0022;;;;N;;;;;
3967 #xFF03 ;FULLWIDTH NUMBER SIGN;Po;0;ET;<wide> 0023;;;;N;;;;;
3968 #xFF05 ;FULLWIDTH PERCENT SIGN;Po;0;ET;<wide> 0025;;;;N;;;;;
3969 #xFF06 ;FULLWIDTH AMPERSAND;Po;0;ON;<wide> 0026;;;;N;;;;;
3970 #xFF07 ;FULLWIDTH APOSTROPHE;Po;0;ON;<wide> 0027;;;;N;;;;;
3971 #xFF0A ;FULLWIDTH ASTERISK;Po;0;ON;<wide> 002A;;;;N;;;;;
3972 #xFF0C ;FULLWIDTH COMMA;Po;0;CS;<wide> 002C;;;;N;;;;;
3973 #xFF0E ;FULLWIDTH FULL STOP;Po;0;CS;<wide> 002E;;;;N;FULLWIDTH PERIOD;;;;
3974 #xFF0F ;FULLWIDTH SOLIDUS;Po;0;ES;<wide> 002F;;;;N;FULLWIDTH SLASH;;;;
3975 #xFF1A ;FULLWIDTH COLON;Po;0;CS;<wide> 003A;;;;N;;;;;
3976 #xFF1B ;FULLWIDTH SEMICOLON;Po;0;ON;<wide> 003B;;;;N;;;;;
3977 #xFF1F ;FULLWIDTH QUESTION MARK;Po;0;ON;<wide> 003F;;;;N;;;;;
3978 #xFF20 ;FULLWIDTH COMMERCIAL AT;Po;0;ON;<wide> 0040;;;;N;;;;;
3979 #xFF3C ;FULLWIDTH REVERSE SOLIDUS;Po;0;ON;<wide> 005C;;;;N;FULLWIDTH BACKSLASH;;;;
3980 #xFF61 ;HALFWIDTH IDEOGRAPHIC FULL STOP;Po;0;ON;<narrow> 3002;;;;N;HALFWIDTH IDEOGRAPHIC PERIOD;;;;
3981 #xFF64 ;HALFWIDTH IDEOGRAPHIC COMMA;Po;0;ON;<narrow> 3001;;;;N;;;;;
3983 #x00AB ;LEFT-POINTING DOUBLE ANGLE QUOTATION MARK;Pi;0;ON;;;;;Y;LEFT POINTING GUILLEMET;;;;
3984 #x2018 ;LEFT SINGLE QUOTATION MARK;Pi;0;ON;;;;;N;SINGLE TURNED COMMA QUOTATION MARK;;;;
3985 #x201B ;SINGLE HIGH-REVERSED-9 QUOTATION MARK;Pi;0;ON;;;;;N;SINGLE REVERSED COMMA QUOTATION MARK;;;;
3986 #x201C ;LEFT DOUBLE QUOTATION MARK;Pi;0;ON;;;;;N;DOUBLE TURNED COMMA QUOTATION MARK;;;;
3987 #x201F ;DOUBLE HIGH-REVERSED-9 QUOTATION MARK;Pi;0;ON;;;;;N;DOUBLE REVERSED COMMA QUOTATION MARK;;;;
3988 #x2039 ;SINGLE LEFT-POINTING ANGLE QUOTATION MARK;Pi;0;ON;;;;;Y;LEFT POINTING SINGLE GUILLEMET;;;;
3990 #x00BB ;RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK;Pf;0;ON;;;;;Y;RIGHT POINTING GUILLEMET;;;;
3991 #x2019 ;RIGHT SINGLE QUOTATION MARK;Pf;0;ON;;;;;N;SINGLE COMMA QUOTATION MARK;;;;
3992 #x201D ;RIGHT DOUBLE QUOTATION MARK;Pf;0;ON;;;;;N;DOUBLE COMMA QUOTATION MARK;;;;
3993 #x203A ;SINGLE RIGHT-POINTING ANGLE QUOTATION MARK;Pf;0;ON;;;;;Y;RIGHT POINTING SINGLE GUILLEMET;;;;
3997 ;;;; vertical margin
3999 ;; although we could go the route of another evil global variable, i would
4000 ;; prefer to keep it functional.
4002 ;;;; todo
4004 ;;; tables
4005 ;; ### HTML table props
4006 ;; ### table row dimensions
4007 ;; ### table border model
4008 ;; ### we need to test what happens in case the available horizontal
4009 ;; space is too narrow to support the current word.
4012 ;;; asorted
4014 ;; ### auf heise.de gibt es wieder wsp glitch
4016 ;;;; marker boxen
4018 ;; now:
4020 ;; - they _always_ render into a single line
4021 ;; - a vertical-align property on the marker is meaningful
4022 ;; - they render to the first / last line of the block's paragraph[s].
4023 ;; - when the block has no paragraph ???
4025 ;;;; word-spacing and letter-spacing:
4027 ;; There are certain things to obey:
4029 ;; a. a letter-spacing other than :normal forbids altering it for paragraph
4030 ;; justification purposes.
4032 ;; b. "When the resultant space between two characters is not the same as the
4033 ;; default space, user agents should not use ligatures."
4035 ;; Which is just plain nuts!
4037 ;; In old German it was customary to increase letter-spacing for headers.
4038 ;; ("sperren"). But ligatures were kept together like:
4040 ;; N e tz k u l t u r
4041 ;; S p a sz o d e r E r n s t ?
4043 ;; There therefore is no way to specify that.
4045 ;; c. what exactly is "between text characters?" Between a letter and a white
4046 ;; space also? Or just between letters?
4049 ;;;;
4051 ;; After all we better do it again in two phases in popo-para.
4053 ;;;;
4055 (defclass lazy-image ()
4058 (defmethod ro/size ((object lazy-image))
4059 (values 20 20 0))
4061 (defmethod closure/clim-device::medium-draw-ro* (medium (self lazy-image) x y)
4062 (clim:draw-rectangle* medium x y (+ x 20) (- y 20) :ink clim:+red+))
4064 ;;;; the containing block
4066 ;; Now we have a couple of style attributes which refer to the width of the
4067 ;; 'containing block' which is a well defined term in the CSS2 dream. We
4068 ;; want computation of this value in css-setup directly. However there are a
4069 ;; few complications:
4071 ;; - The width of the containing block is not always known at the time we
4072 ;; set up style as for instance while collecting a table or computing its
4073 ;; minimum/maximum. Also during min/max width calculation we want to
4074 ;; assume 0 for each percentage value which refers to the width of the
4075 ;; containing block.
4077 ;; However, if we assume that we have a somewhat decent implementation of
4078 ;; on the fly changing this property and having the change properagated
4079 ;; down into 'child' styles quickly we can get away with just asserting
4080 ;; zero for the width of the containing block first and just set it to the
4081 ;; new value afterwards. Also by doing this we can easily implement window
4082 ;; size changes by just flipping the width of the BODY element.
4084 ;; For now we just take a low tech approach and somehow store the containing
4085 ;; block with the cooked-style. Upon accessing e.g. width we simply
4086 ;; recompute in case of percentage values.
4091 ;;; all properties which refer to width of containing block
4093 ;; margin-* width of containing block
4094 ;; padding-* width of containing block
4095 ;; width width of containing block
4097 ;; max-width width of containing block
4098 ;; min-width width of containing block
4100 ;; left width of containing block
4101 ;; right width of containing block
4102 ;; text-indent width of containing block
4104 ;;; properties which refer to height of containing block
4106 ;; bottom height of containing block
4107 ;; top height of containing block
4108 ;; height see prose
4109 ;; max-height height of containing block
4110 ;; min-height height of containing block
4114 ;; For now we just add the style of the containing block to a cooked style
4115 ;; and wrap methods around the readers to resolve the percentage widthen.
4116 ;; We need however look out for possible replaced elements.
4118 ;; Later we think about a kind of change propergation. So that we can simply
4119 ;; set a style attribute and have all the other style invalidated or
4120 ;; recomputed. The cooked-style object themself then inform the renderer
4121 ;; what ever.
4123 (progn
4124 #.`(progn
4125 ,@(loop for foo in '(cooked-style-text-indent
4126 cooked-style-padding-left
4127 cooked-style-padding-right
4128 cooked-style-padding-top
4129 cooked-style-padding-bottom
4130 cooked-style-margin-top
4131 cooked-style-margin-bottom
4132 cooked-style-max-width
4133 cooked-style-min-width
4134 cooked-style-left
4135 cooked-style-right)
4136 collect
4137 `(defmethod ,foo :around ((style css::cooked-style))
4138 (let ((value (call-next-method)))
4139 (if (css:percentage-p value)
4140 (maybe-resolve-percentage
4141 value
4142 (cooked-style-width (slot-value style 'css::%containing-block)))
4143 value))))))
4145 ;;; ### hmm we still wind up with superfluous kern-chunks ...
4146 ;;; ### this is because we still wind up with letter-spacing =0
4148 ;;; ### height
4150 ;;;; 10.3 Computing widths and margins
4152 ;; while we are at it we should compute left, right, top, bottom as well.
4154 (defmethod cooked-style-margin-left :around ((style css::cooked-style))
4155 (with-slots (computed-margin-left) style
4156 (or computed-margin-left
4157 (progn (compute-widths-and-margins style)
4158 computed-margin-left))))
4160 (defmethod cooked-style-margin-right :around ((style css::cooked-style))
4161 (with-slots (computed-margin-right) style
4162 (or computed-margin-right
4163 (progn (compute-widths-and-margins style)
4164 computed-margin-right))))
4166 (defmethod cooked-style-width :around ((style css::cooked-style))
4167 (with-slots (computed-width) style
4168 (setf computed-width nil) ;### for tables
4169 (or computed-width
4170 (progn (compute-widths-and-margins style)
4171 computed-width))))
4173 (defmethod cooked-style-height :around ((style css::cooked-style))
4174 (with-slots (computed-height) style
4175 (or computed-height
4176 (progn (compute-widths-and-margins style)
4177 computed-height))))
4179 (defun compute-widths-and-margins (style)
4180 "Given a cooked style compute the effective values for margins and width."
4182 (format *trace-output* "~&compute-widths-and-margins: we have ~s with containing block = ~s.~%"
4183 style (slot-value style 'css::%containing-block))
4185 (let* ((containing-block-style
4186 (slot-value style 'css::%containing-block))
4187 (cb.width
4188 (if containing-block-style
4189 (cooked-style-width containing-block-style)
4190 *canvas-width*)) ;###
4191 (replaced-object (replaced-element-p *document* *device*
4192 (slot-value style 'css::%element)))
4193 (float (cooked-style-float style))
4194 (ml (slot-value style 'css::margin-left))
4195 (bl (slot-value style 'css::border-left-width))
4196 (pl (slot-value style 'css::padding-left))
4197 (wd (slot-value style 'css::width))
4198 (pr (slot-value style 'css::padding-right))
4199 (br (slot-value style 'css::border-right-width))
4200 (mr (slot-value style 'css::margin-right))
4201 (hd (slot-value style 'css::height)) )
4203 (setf ml (maybe-resolve-percentage ml cb.width))
4204 (setf pl (maybe-resolve-percentage pl cb.width))
4205 (setf wd (maybe-resolve-percentage wd cb.width))
4206 (setf pr (maybe-resolve-percentage pr cb.width))
4207 (setf mr (maybe-resolve-percentage mr cb.width))
4209 (when (css:percentage-p hd)
4210 ;; | height: <percentage>
4211 ;; | Specifies a percentage height. The percentage is calculated with respect
4212 ;; | to the height of the generated box's containing block. If the height of
4213 ;; | the containing block is not specified explicitly (i.e., it depends on
4214 ;; | content height), the value is interpreted like 'auto'.
4216 ;; I remember from HTML that a percentage attribute on an IMG element
4217 ;; should be treated relative to the canvas.
4219 (let ((cb.hd (cooked-style-height containing-block-style)))
4220 (cond ((not (realp cb.hd))
4221 (setf hd :auto))
4223 (setf hd (maybe-resolve-percentage hd cb.hd))))))
4226 (with-slots (computed-margin-left computed-width computed-margin-right computed-height) style
4228 (when replaced-object
4229 (multiple-value-bind (r.w r.h r.d)
4230 (replaced-object-dimensions replaced-object wd hd)
4231 (setf wd r.w
4232 hd (+ r.h r.d))
4233 ;; ### now this is bogus --- it does not belong here.
4234 #+NIL
4235 (ro/resize replaced-object r.w (+ r.h r.d)) ))
4237 (cond ((cooked-style-block-element-p style)
4238 (cond ((eq float :none)
4239 ;; 10.3.3 Block-level, non-replaced elements in normal flow
4240 ;; 10.3.4 Block-level, replaced elements in normal flow
4242 (setf (values ml bl pl wd pr br mr)
4243 (resolve-widthen style cb.width
4244 ml bl pl wd pr br mr)) )
4246 ;; 10.3.5 Floating, non-replaced elements
4247 ((null replaced-object)
4248 (when (eql wd :auto) (setf wd 0))
4249 (when (eql ml :auto) (setf ml 0))
4250 (when (eql mr :auto) (setf mr 0)))
4252 ;; 10.3.6 Floating, replaced elements
4254 (when (eql ml :auto) (setf ml 0))
4255 (when (eql mr :auto) (setf mr 0))) ))
4257 ;; 10.3.1 Inline, non-replaced elements
4258 ((null replaced-object)
4259 (when (eql ml :auto) (setf ml 0))
4260 (when (eql mr :auto) (setf mr 0)) )
4262 ;; 10.3.2 Inline, replaced elements
4264 (when (eql ml :auto) (setf ml 0))
4265 (when (eql mr :auto) (setf mr 0))) )
4267 (setf computed-margin-left ml
4268 computed-margin-right mr
4269 computed-width wd
4270 computed-height hd)
4271 (values ml wd mr)) ))
4273 ;; ### In case of floating boxen the vertical is properly still not handled
4274 ;; correctly. This is because we handle floating objects in block context
4275 ;; as a paragraph, which is not correct.
4277 (defmethod cooked-style-margin-left :around ((style css::cooked-style))
4278 (with-slots (computed-margin-left) style
4279 (setf computed-margin-left nil) ;### for tables
4280 (or computed-margin-left
4281 (progn (compute-widths-and-margins style)
4282 computed-margin-left))))
4284 (defmethod cooked-style-margin-right :around ((style css::cooked-style))
4285 (with-slots (computed-margin-right) style
4286 (setf computed-margin-right nil) ;### for tables
4287 (or computed-margin-right
4288 (progn (compute-widths-and-margins style)
4289 computed-margin-right))))
4291 (defmethod cooked-style-width :around ((style css::cooked-style))
4292 (with-slots (computed-width) style
4293 (setf computed-width nil) ;### for tables
4294 (or computed-width
4295 (progn (compute-widths-and-margins style)
4296 computed-width))))
4298 (defmethod cooked-style-height :around ((style css::cooked-style))
4299 (with-slots (computed-height) style
4300 (or computed-height
4301 (progn (compute-widths-and-margins style)
4302 computed-height))))
4304 ;; ### Note: there is perhaps a design flaw: On one hand we pass x1,
4305 ;; x2 down all the way on the other hand chunks know their width
4308 ;;; We now need a change protocol ...
4310 ;; something like
4311 ;; update-block-box block-box
4312 ;; it should then figure out automatically what changed.
4314 ;; Better yet. A block box being dirty just means that its content just moves somehow.
4316 (defun table-borders-lessp (b1.origin b1.style b1.width b2.origin b2.style b2.width)
4317 ;; | The following rules determine which border style "wins" in case of a conflict:
4318 (cond
4319 ;; | 1. Borders with the 'border-style' of 'hidden' take precedence over
4320 ;; | all other conflicting borders. Any border with this value
4321 ;; | suppresses all borders at this location.
4322 ((eql b1.style :hidden) nil)
4323 ((eql b2.style :hidden) t)
4324 ;; | 2. Borders with a style of 'none' have the lowest priority. Only if
4325 ;; | the border properties of all the elements meeting at this edge are
4326 ;; | 'none' will the border be omitted (but note that 'none' is the
4327 ;; | default value for the border style.)
4328 ((eql b1.style :none) t)
4329 ((eql b2.style :none) nil)
4330 ;; | 3. [...], then narrow borders are discarded in favor of wider
4331 ;; | ones.
4332 ((< b1.width b2.width) t)
4333 ((> b1.width b2.width) nil)
4334 ;; | If several have the same 'border-width' than styles are
4335 ;; | preferred in this order: 'double', 'solid', 'dashed', 'dotted',
4336 ;; | 'ridge', 'outset', 'groove', and the lowest: 'inset'.
4337 ((> (position b1.style '(:double :solid :dashed :dotted :ridge :outset :groove :inset))
4338 (position b2.style '(:double :solid :dashed :dotted :ridge :outset :groove :inset)))
4340 ((< (position b1.style '(:double :solid :dashed :dotted :ridge :outset :groove :inset))
4341 (position b2.style '(:double :solid :dashed :dotted :ridge :outset :groove :inset)))
4342 nil)
4343 ;; | 4. If border styles differ only in color, then a style set on a
4344 ;; | cell wins over one on a row, which wins over a row group, column,
4345 ;; | column group and, lastly, table.
4346 ((> (position b1.origin '(:cell :row :row-group :column :column-group :table))
4347 (position b2.origin '(:cell :row :row-group :column :column-group :table)))
4349 ((> (position b1.origin '(:cell :row :row-group :column :column-group :table))
4350 (position b2.origin '(:cell :row :row-group :column :column-group :table)))
4351 nil)
4353 ;; pick one:
4354 (zerop (random 2))
4355 t)))
4357 (defun combine-table-borders (b1.origin b1.style b1.width b1.color
4358 b2.origin b2.style b2.width b2.color)
4359 (if (table-borders-lessp b1.origin b1.style b1.width b2.origin b2.style b2.width)
4360 (values b2.origin b2.style b2.width b2.color)
4361 (values b1.origin b1.style b1.width b1.color)))
4363 (defun table-borders (table)
4364 "Returns two 2D arrays representing the borders existing in a table according to the collapsed border model."
4365 (let* ((nr (table-number-of-rows table))
4366 (nc (table-number-of-columns table))
4367 (hborders (make-array (list (1+ nr) nc)
4368 :initial-element '(nil :none 0 nil)))
4369 (vborders (make-array (list nr (1+ nc))
4370 :initial-element '(nil :none 0 nil))))
4371 (labels ((stuff-border (ri ci rs cs origin style)
4372 ;; top
4373 (loop for j from ci below (+ ci cs) do
4374 (stuff-border-1 hborders ri j origin
4375 (cooked-style-border-top-style style)
4376 (cooked-style-border-top-width style)
4377 (cooked-style-border-top-color style)))
4378 ;; bottom
4379 (loop for j from ci below (+ ci cs) do
4380 (stuff-border-1 hborders (+ ri rs) j origin
4381 (cooked-style-border-bottom-style style)
4382 (cooked-style-border-bottom-width style)
4383 (cooked-style-border-bottom-color style)))
4384 ;; left
4385 (loop for i from ri below (+ ri rs) do
4386 (stuff-border-1 vborders i ci origin
4387 (cooked-style-border-left-style style)
4388 (cooked-style-border-left-width style)
4389 (cooked-style-border-left-color style)))
4390 ;; right
4391 (loop for i from ri below (+ ri rs) do
4392 (stuff-border-1 vborders i (+ ci cs) origin
4393 (cooked-style-border-right-style style)
4394 (cooked-style-border-right-width style)
4395 (cooked-style-border-right-color style))))
4397 (stuff-border-1 (array ri ci origin style width color)
4398 (setf (aref array ri ci)
4399 (multiple-value-list (multiple-value-call #'combine-table-borders
4400 (values-list (aref array ri ci))
4401 (values origin style width color))))) )
4402 ;; Do the border of the table
4403 (stuff-border 0 0 nr nc :table (table-style table))
4404 ;; Do border of column groups and columns
4405 (let ((ci 0)
4406 (nrows (table-number-of-rows table)))
4407 (loop for col-group in (table-column-groups table) do
4408 (let ((ci0 ci))
4409 (loop for col in (table-column-group-columns col-group) do
4410 (when (table-column-style col)
4411 (stuff-border 0 ci nrows 1 :column (table-column-style col)))
4412 (incf ci))
4413 (when (table-column-group-style col-group)
4414 (stuff-border 0 ci0 nrows (- ci ci0) :column-group (table-column-group-style col-group))))))
4415 ;; Do border of row groups and rows
4416 (let ((ri 0)
4417 (ncols (table-number-of-columns table)))
4418 (loop for row-group in (table-row-groups table) do
4419 (let ((ri0 ri))
4420 (loop for row in (table-row-group-rows row-group) do
4421 (when (table-row-style row)
4422 (stuff-border ri 0 1 ncols :row (table-row-style row)))
4423 (incf ri))
4424 (when (table-row-group-style row-group)
4425 (stuff-border ri0 0 (- ri ri0) ncols :row-group (table-row-group-style row-group))))))
4426 ;; Do the border of the cells
4427 (map-table table
4428 (lambda (cell ri ci)
4429 (stuff-border ri ci (table-cell-rowspan cell) (table-cell-colspan cell)
4430 :cell (table-cell-style cell))))
4431 (values
4432 hborders
4433 vborders) )))
4435 ;;; Okay that is only half of the work needed. The other half is
4436 ;;; actually drawing these wicked borders.
4438 ;;; Drawing table borders
4440 ;;; Collapsing border model
4442 ;; In the collapsing border model a cell cannot have margins and its
4443 ;; border width is only accounted half for since it shares the border
4444 ;; with its neighbors. The table itself probably can have margins but
4445 ;; the same calculations wrt the border applies.
4447 ;; Uff we will also get floating tables and stuff ... But then doesn't
4448 ;; float force the display to block?
4450 ;;; Overview
4452 ;; In the first pass we cope with generated content, markers, run-in
4453 ;; and compact stuff and just generate a kind of formatting object
4454 ;; hierachy.
4456 ;; Tables also get collected here as well as whitespace handling and
4457 ;; text-transform and first-line, first-letter stuff.
4459 ;; where are the columns supposed to be
4462 (defun popo-table-stuff (q ss)
4463 ;; this is called when every we see some table element.
4464 (popo-collect-table q ss)
4468 ;;;; TABLE TODO
4470 ;; Collecting a table should happen much earlier. We want it in the
4471 ;; same stage we build the block-boxen and para-boxen.
4473 ;; Then we could also cope with situations like just having a few
4474 ;; table cells without having a table around it.
4476 ;; Then CSS-2's idea of caption is a. nuts and b. hopelessly
4477 ;; underspecified. c. somewhat incompatible with HTML-4.01.
4479 ;; The implication of the CSS table model is that cells have neigher
4480 ;; margin nor border.
4482 ;; Also my minmax should be more sensible to actually specified
4483 ;; margins.
4485 ;; ### we still have a probelm with width and margin et al. Our
4486 ;; accessors are supposed to return the actual computed dimensions,
4487 ;; yet dimensions of tables and their cells are are computed in the
4488 ;; table type setter.
4490 ;; so either do it there for tables and table cells also. Or do as we
4491 ;; previously did and compute the width and margins while typesetting.
4492 ;; But:
4494 ;; ### Since tables observe floating boxen in a strange way, we generally
4495 ;; cannot compute width et al outside the renderer.
4497 ;; So we might want to switch back. We introduced this because markers
4498 ;; have a containing block which is different to the containing block
4499 ;; of the line box they are rendered into. So we probably should
4500 ;; switch it back insofar as that the width attribute is not fully
4501 ;; computed and that percentage references to the containig block
4502 ;; refer this containing block not via a style-node but via a
4503 ;; block-box or something similar.
4506 (defun sgml-unparse (element sink)
4507 (cond ((text-element-p element)
4508 (princ (rod-string (element-text element)) sink))
4510 (format sink "<~A" (element-gi element))
4511 (do ((q (sgml:pt-attrs element) (cddr q)))
4512 ((null q))
4513 (format sink " ~A=~S" (car q) (rod-string (cadr q))))
4514 (format sink "~%>")
4515 (dolist (child (element-children element))
4516 (sgml-unparse child sink))
4517 (unless (member (element-gi element) '(:img :br))
4518 (format sink "</~A>" (element-gi element))))))
4520 ;;; BUGS
4522 ;; ### Our handling of <BR><BR> is slightly off since netscape inserts a
4523 ;; full blank line, we do not.
4525 ;; ### That is <BR> is mapped to a block level element instead of to BR:before{content:"\0a";}
4526 ;; And then \0A is not handled correctly for content.
4528 ;; ### the table typesetter must acknowledge text-align.
4530 ;; ### background on table is not honoured.
4531 ;; ### parser incompatiblity:
4532 ;; <td> <div> <td> NS: <td> <div> </div> </td> <td>
4533 ;; we: something other
4536 ;; ### incompat: NS still does not parse correctly.
4537 ;; e.g. is <P>x<TABLE></TABLE> parsed as
4538 ;; <P>x<TABLE></TABLE></P>
4539 ;; instead of <P>x</P><TABLE></TABLE>
4540 ;; same with opera.
4542 ;; ### we need a facility to find the baseline of a block-box
4543 ;; [this is similar to the marker boxen issue]
4546 ;; ### by accident i hit an incompatibility: NS apperently reads
4547 ;; <th<th> as <TH> while I'll read <TH><TH>
4549 ;; ### somebody is so kind and draws a border around our tables, which
4550 ;; we do not want, since tables draw their borders themself. Guess tables
4551 ;; are wraped into a superfluous block-box.
4553 ;; ### somehow the background and other style of the body gets lost ...
4555 ;; ### For no appearent reasons we fail the vertical margin test.
4556 ;; --> we might revert to an evil global variable kludge.
4557 ;; (although this is bad in the context of dynamic
4558 ;; effects).
4559 ;; I guess this is fixed now, but need to check.
4561 ;; ### "17.2.1 Anonymous table objects" is still not fully e.g. a
4562 ;; single table-cell element will not automatically infer the needed
4563 ;; stuff.
4565 ;; ### table-footer-group
4566 ;; ### table-header-group
4567 ;; ### table-caption
4568 ;; ### inline-table
4569 ;; ### 17.2.1 Anonymous table objects
4570 ;; ### visibility
4571 ;; ### iirc HTML has some abbreviation kludge to expand a COLGROUP into multiple columns.
4573 ;; ### For performance we definitely should optimize the case that there is no
4574 ;; first-line pseudo element at all!
4576 ;; ### before/after on non-inline boxen
4577 ;; ### marker
4578 ;; ### run-in
4579 ;; ### compact
4581 ;; ### make sure setup-style is called _once_.
4583 ;; ### also from looking at the style stacks: first-line style is only applied to blocks within blocks -- art.
4585 ;; ### we need clear
4587 ;; ### since we might duplicate work anyway we should always recurse into blocks
4588 ;; by means of po-para.
4590 ;; ### see if those pseudo elements are always closed.
4591 ;; hmm ...
4593 ;; ### Q: where exactly should first-letter and first-line apply?
4595 ;; ### Does run-in alter the first-letter / first-line of an
4596 ;; paragraph? That is: where exactly has a run-in to be inserted?
4598 ;; ### think about hyphenation -- but probably only hyphenate the
4599 ;; words where that turns out neccessary.
4601 ;; ### We should think about performance. I imagine that all this
4602 ;; first-letter and first-line stuff costs quite a bit of In 99%
4603 ;; of the cases there is no first-line or first-letter style
4604 ;; involved.
4606 ;; ### block level replaced elements.
4608 ;; ### systematically collect all properies about to be handled here
4609 ;; and skim the code to make sure they are implemented properly.
4611 ;; ### white-space over a paragraph is constant, so optimize this case
4612 ;; also.
4614 ;; ### text-indent: provide for it also in minmax
4615 ;; ### text-indent: only do it for the first-line of a paragraph
4616 ;; (use same definition as first-line).
4617 ;; ### text-indent: how to justify? guess we just include it.
4618 ;; ### text-indent: what exactly is its containing block?
4620 ;; ### text-shadow is just nuts!
4622 ;; ### now open chunks and stuff must either
4623 ;; a. get their assigned values for width et al
4624 ;; b. get a reference to the 'containing block'
4625 ;; OR:
4626 ;; c. we put resolving width into my-setup-style
4628 ;; ### handle all content variants
4630 ;; ### test markers attached to floating boxen
4632 ;; ### we have problems with para-boxen entirely consisting out of floating
4633 ;; boxen as they shouldn't be considered exactly the same.
4634 ;; [what would be a good solution to this?]
4635 ;; so we need to reinstall the first-line stuff.
4636 ;; so maybe just maybe we we need this dangling white-space thing again ;(
4638 ;; ### drawing order in case of floating boxen.
4640 ;;; popo pass
4642 ;; ### think about old hacks for making stuff lazy and on-demand.
4643 ;; ### possibly empty paras
4644 ;; ### possibly proper BR handling
4645 ;; ### dangling white space
4646 ;; ### first char condition should end with a white space.
4648 ;; ### in vertically-align-line haben wir wieder nicht-gepaarte chunks!
4649 ;; z.b. www.w3.org ;-(
4652 ;; ### justify
4654 ;; ### in PRE verschwinden leere zeilen.
4656 ;; ### in PRE: vernuenftige tab behandlung.
4658 ;; ### clear auf run-in, compact;
4660 ;; ### Question:
4661 ;; when i have
4662 ;; <p><span style='border: 1px solid'>foo<span style='display:block'></span>bar</span></p>
4663 ;; how is that supposed to be rendered. should the border be open at the RHS of "foo" or not?
4666 ;; BUG: word-spacing probably also has to apply to &nbsp;.
4668 ;; BUG: generally we flush too late, we should flush as soon any non
4669 ;; block content about to be rendered. Currently we solve that by
4670 ;; flushing in add-rune* which is quite expensive. We should post-pone
4671 ;; that until the first word is finished.
4673 ;; BUG: Also generally we should keep more track of the various
4674 ;; states like first line, first char, first word etc to spare the
4675 ;; possibly expensive test we have now.
4677 ;; BUGS
4679 ;; . The last white-spaces are not removed.
4680 ;; => That is: a white space has only to be emitted when there is
4681 ;; both black content before and after it.
4683 ;; ### <http://www.meyerweb.com/eric/css/edge/curvelicious/demo.html>
4684 ;; probably a similiar drawing order issue. otherwise: okay.
4686 ;; ### <http://www.meyerweb.com/eric/css/edge/boxpunch/demo.html>
4687 ;; drawing order issue.
4689 ;; ### <http://www.meyerweb.com/eric/css/edge/> does not look right.
4690 ;; yes, abs positioned boxes are missing.
4692 ;; ### <http://lists.w3.org/Archives/Public/www-svg/2000Mar/0039.html> is fine now besides <PRE>
4694 ;; ### <http://research.yale.edu/lawmeme/modules.php?name=News&file=article> round borders are
4695 ;; missing (background or something?)
4696 ;; Yep, background on the TD cells that is.
4698 ;; ### we just came across the following:
4700 ;; #<LINE-FRAGMENT {488267D5}> is an instance of type LINE-FRAGMENT
4701 ;; it has the following slots:
4702 ;; CHUNKS: (#{<P>:first-line} #{<P>:first-letter} #{"T"}
4703 ;; #{</P>:first-letter} #{"he"} #{" "} #{"style"} #{" "}
4704 ;; #{"declarations"} #{" "} #{"which"} #{" "} #{"apply"}
4705 ;; #{" "} #{"to"} #{" "} #{"the"} #{" "} #{"text"} #{" "}
4706 ;; #{"below"} #{" "} #{"are:"})
4707 ;; BLOCK-STYLE: #<COOKED-STYLE for P>
4708 ;; X2: 780
4709 ;; X1: 20
4710 ;; That is the line is probably closed. Especially the </P:firstline> is missing.
4711 ;; This is kind of "fixed".
4713 ;; ### we fail with more than one TBODY, or a present THEAD/TFOOT.
4715 ;; ### strange lossage with <http://saphir.local:8000/~gilbert/>
4720 ;;; Solved
4722 ;; test44: a few problems with inline images:
4723 ;; . vertical border and padding is not accounted for in line height calculation
4724 ;; . border and image coordinates are out of synch vertically.
4725 ;; => We need to talk again about geometry.
4727 ;; The "bottom" of a replaced object is the bottom including margin, padding and border.
4728 ;; The "top" likewise.
4730 ;; Note: This is different to non replaced inline elements, where
4731 ;; margins and border are not included in the line height calculation.
4733 ;; The generated fo sequence:
4735 ;; #<BLOCK-BOX :HTML
4736 ;; #<BLOCK-BOX :BODY
4737 ;; #<BLOCK-BOX :P
4738 ;; #<PARA-BOX (#{<IMG>} #<REPLACED-OBJECT-CHUNK {481EF905}>
4739 ;; #{</IMG>})>>>>
4742 ;; => It seems that we should not generate open and close chunks
4743 ;; for IMG, but have that directly in the replaced object chunk
4744 ;; for special threatment.
4746 ;; But then dis defeats block level replaced objects, which are
4747 ;; now nicely represented by:
4749 ;; #<BLOCK-BOX :HTML
4750 ;; #<BLOCK-BOX :BODY
4751 ;; #<BLOCK-BOX :P
4752 ;; #<BLOCK-BOX :IMG
4753 ;; #<PARA-BOX (#<REPLACED-OBJECT-CHUNK {488BE5AD}>)>>>>>
4755 ;; and rendered almost correct.
4758 ;; further clean up: e.g. popo-block always returns a list of one element
4759 ;; [this ripples up to e.g. floating-chunk-content etc]
4761 ;; in qc.html we still have a problem with vertical align.
4763 ;; <http://www.meyerweb.com/eric/css/inline-hades.html> seems to be fine now.
4765 ;; quite things happen:
4766 ;; 0: (COMPUTE-WIDTHS-AND-MARGINS #<CSS::COOKED-STYLE for BODY>)
4767 ;; 1: (COMPUTE-WIDTHS-AND-MARGINS #<CSS::COOKED-STYLE for BODY>)
4768 ;; 2: (COMPUTE-WIDTHS-AND-MARGINS #<CSS::COOKED-STYLE for HTML>)
4770 ;; why does COMPUTE-WIDTHS-AND-MARGINS on BODY call
4771 ;; COMPUTE-WIDTHS-AND-MARGINS on BODY again?
4774 (defun describe-table (table)
4775 (fresh-line)
4776 (with-slots (column-groups row-groups) table
4777 (format T "Column groups:~%")
4778 (dolist (cg column-groups)
4779 (format T " Group~%")
4780 (dolist (col (slot-value cg 'columns))
4781 (format T " Column [min ~D max ~D]~%"
4782 (slot-value col 'minimum-width)
4783 (slot-value col 'maximum-width))))
4784 (format T "Row groups:~%")
4785 (dolist (rg row-groups)
4786 (format T " Rowgroup~%")
4787 (dolist (row (slot-value rg 'rows))
4788 (format T " Row~%")
4789 (dolist (cell (slot-value row 'cells))
4790 (apply #'format T " Cell [colspan ~D rowspan ~D col-index ~D]~%"
4791 (mapcar (curry #'slot-value cell) '(colspan rowspan col-index))))))))
4793 ;;;;
4795 (defparameter *debug-tex-p* nil
4796 "Whether to debug the so called 'Tex Mode'.")
4798 (defparameter *visible-hyphens-p* nil)
4800 (defvar *hyphenation-table* nil)
4802 (defun hyphenation-table ()
4803 (or *hyphenation-table*
4804 (setf *hyphenation-table*
4805 (read-hyphen-table "file://closure/resources/patterns/english.ptn"))))
4807 (defun hyphenate-items (items w)
4808 "This takes a chunk list and applies hyphenation it it."
4809 ;; The first thing we need to do is to identify words. Luckily words
4810 ;; are already separated by disk-chunks (white stuff).
4812 ;; ### the proper half-chunks are missing.
4813 (let ((curword nil)
4814 (words nil))
4815 (labels ((spill-word ()
4816 (when curword
4817 (push (reverse curword) words))
4818 (setf curword nil)))
4819 (loop for i from 0
4820 for x in items do
4821 (typecase x
4822 (black-chunk
4823 (let ((data (black-chunk-data x)))
4824 (loop for j from 0
4825 for c across data do
4826 (cond ((or (rune<= #/a c #/z)
4827 (rune<= #/A c #/Z))
4828 (push (list i j c) curword))
4830 (spill-word))))))
4831 (disc-chunk
4832 (spill-word))
4833 (otherwise
4835 (spill-word)
4836 ;; #+NIL
4837 (let ((hps nil))
4838 (dolist (word words)
4839 (let* ((s (map 'string (lambda (x) (rune-char (third x))) word))
4840 (z (hyphen-points (hyphenation-table) s)))
4841 (dolist (k (reverse z)) (push (elt word k) hps))))
4842 ;; an assert a day keeps the surprise away.
4843 (assert (every #'<= (mapcar #'first hps) (cdr (mapcar #'first hps))))
4844 ;;(setf hps (reverse hps))
4845 ;; aha, bug! bug! bug!
4846 (let ((res nil)
4847 (ss nil))
4848 (print hps)
4849 (loop for i from 0
4850 for item in items do
4851 (typecase item
4852 (black-chunk
4853 (cond ((eql i (caar hps))
4854 (labels ((foo (j)
4855 (cond ((not (eql i (caar hps)))
4856 (push (cons-black-chunk :style (black-chunk-style item)
4857 :data (subseq (black-chunk-data item) j))
4858 res))
4860 (let ((j2 (second (pop hps))))
4861 (push (cons-black-chunk :style (black-chunk-style item)
4862 :data (subseq (black-chunk-data item) j j2))
4863 res)
4864 (push
4865 (make-instance 'disc-chunk
4866 :%before (cons
4867 (cons-black-chunk :style (black-chunk-style item)
4868 :data (rod "-"))
4869 (mapcar (lambda (k)
4870 (make-half-close-chunk (bounding-chunk-pt k)
4871 (bounding-chunk-style k)))
4872 ss))
4873 :%after (mapcar (lambda (k)
4874 (make-half-open-chunk (bounding-chunk-pt k)
4875 (bounding-chunk-style k)))
4876 (reverse ss))
4877 :%here (if *visible-hyphens-p*
4878 (list
4879 (cons-black-chunk :style (black-chunk-style item)
4880 :data (rod "-")))
4881 nil))
4882 res)
4883 (foo j2))))))
4884 (foo 0)))
4886 (push item res))))
4887 (open-chunk
4888 (push item ss)
4889 (push item res))
4890 (close-chunk
4891 (pop ss)
4892 (push item res))
4894 (push item res))))
4895 (setf items (reverse res))))
4898 #|| ;
4899 (dolist (hp (reverse hps))
4900 (destructuring-bind (i j c) (elt word hp)
4901 (declare (ignore c))
4902 (let ((chunk (elt items i)))
4903 (setf items (append (subseq items 0 i)
4904 (list (cons-black-chunk :style (black-chunk-style chunk)
4905 :data (subseq (black-chunk-data chunk) 0 j))
4906 (make-instance 'disc-chunk
4907 :%before (list
4908 (cons-black-chunk :style (black-chunk-style chunk)
4909 :data (rod "-")))
4910 :%after nil
4911 :%here nil)
4912 (cons-black-chunk :style (black-chunk-style chunk)
4913 :data (subseq (black-chunk-data chunk) j)))
4914 (subseq items (+ i 1)) )))))
4918 (when *debug-tex-p*
4919 (format *trace-output* "#### orig items = ~S.~%" items))
4920 (let* ((tex-nodes (items-to-tex-nodes items))
4921 (tex-items (texpara::format-paragraph tex-nodes w)))
4922 (when *debug-tex-p*
4923 (format *trace-output* "#### tex nodes = ~S.~%" tex-nodes))
4924 (cond ((not (null tex-items))
4925 (let (res)
4926 (dolist (line tex-items)
4927 (when *debug-tex-p*
4928 (format *trace-output* "#### output line = ~S.~%" line))
4929 (setf res (append res
4930 (if (not (null res))
4931 (list
4932 (make-instance 'disc-chunk
4933 :%before nil :%after nil :%here nil :forcep t))
4934 nil)
4935 (tex-line-to-items line))))
4936 res))
4938 ;; paragraph typesetter was not happy with the result.
4939 items))) )))
4941 (defun items-to-tex-nodes (items)
4942 (remove nil
4943 (map 'list (lambda (item)
4944 (etypecase item
4945 (black-chunk
4946 (cond ((equalp (black-chunk-data item) (rod " "))
4947 (texpara::make-white-space-glue (chunk-width item)))
4949 (texpara::make-box :width (chunk-width item) :data item))))
4950 ((or bounding-chunk replaced-object-chunk kern-chunk)
4951 (texpara::make-box :width (chunk-width item) :data item))
4952 (floating-chunk
4954 (disc-chunk
4955 (cond #+NIL
4956 ((and (black-chunk-p (car (disc-chunk-here item)))
4957 (null (cdr (disc-chunk-here item)))
4958 (equalp (black-chunk-data (car (disc-chunk-here item))) (rod " ")))
4959 (texpara::make-white-space-glue (chunk-width (car (disc-chunk-here item)))))
4961 (texpara::make-discretionary
4962 :pre (items-to-tex-nodes (disc-chunk-before item))
4963 :post (items-to-tex-nodes (disc-chunk-after item))
4964 :no (items-to-tex-nodes (disc-chunk-here item))))))
4966 items)))
4968 (defun tex-line-to-items (line)
4969 (map 'list (lambda (node)
4970 (etypecase node
4971 (texpara::box
4972 (texpara::box-data node))
4973 (texpara::glue
4974 (make-kern-chunk (+ (texpara::glue-width node) (texpara::glue-assigned node))))))
4975 line))
4978 ;;; Now what needs to be done is:
4980 ;; - texpara should be able to cope with more general \discretionary nodes
4981 ;; That is one of those which contain glue nodes itself.
4985 ;; $Log: renderer2.lisp,v $
4986 ;; Revision 1.20 2007-07-01 12:16:44 dlichteblau
4987 ;; Patch by Christophe Rhodes on closure-devel <87ejk2sngi.fsf@cantab.net>
4989 ;; Revision 1.19 2007/01/05 23:10:33 emarsden
4990 ;; Fix rendering of preformatted content.
4992 ;; Revision 1.18 2007/01/05 11:19:30 crhodes
4993 ;; Rune fixes for TeX Mode
4995 ;; Revision 1.17 2006/12/30 15:13:55 emarsden
4996 ;; - use CL from Closure packages
4997 ;; - minor rod fixes
4998 ;; - move PARSE-X11-COLOR from clim-user to ws/x11 package
5000 ;; Revision 1.16 2006/12/29 21:29:39 dlichteblau
5002 ;; Use CXML's rune implementation and XML parser.
5004 ;; Revision 1.15 2006/11/06 19:43:01 thenriksen
5005 ;; Remove compiler-killing evil character from comment.
5007 ;; Revision 1.14 2005/08/25 15:03:54 crhodes
5008 ;; turn off table debugging
5010 ;; Revision 1.13 2005/08/08 19:28:20 crhodes
5011 ;; tables.lisp isn't loaded by the .asd file. Move *debug-tables* to
5012 ;; renderer2.lisp instead.
5014 ;; Revision 1.12 2005/07/19 20:42:09 emarsden
5015 ;; More removal of spurious debugging output, and conditionalization of
5016 ;; some output on *debug-tables*
5018 ;; Revision 1.11 2005/07/17 09:35:47 emarsden
5019 ;; Reference hyphenation table via a file:// URL.
5021 ;; Revision 1.10 2005/07/13 13:44:55 crhodes
5022 ;; Make images work, more or less.
5024 ;; * restore horrible grecording hack for (medium-)draw-ro*
5026 ;; * make direct drawing of images to x11 work with my X server (32bpp even
5027 ;; for 24-depth images)
5029 ;; Obviously this should turn into proper clim support for images, at which
5030 ;; point this horribleness can go away. However, this now basically works
5031 ;; for me, modulo compiler consistency strangeness at startup.
5033 ;; Revision 1.9 2005/07/11 15:57:56 crhodes
5034 ;; Complete the renaming *MEDIUM* -> *PANE*.
5036 ;; Panes are CLIM extended-streams, and remember output to them in output
5037 ;; records. Mediums are much simpler, and don't have this kind of
5038 ;; memory. So, though the same drawing functions (DRAW-TEXT, DRAW-LINE)
5039 ;; can have the same initial effect applied to a pane and a medium, the
5040 ;; output-record state is very different.
5042 ;; Revision 1.8 2005/07/10 11:18:35 emarsden
5043 ;; Distinguish between pane and medium in the CLIM GUI. This should
5044 ;; fix image display.
5046 ;; Revision 1.7 2005/03/13 18:03:25 gbaumann
5047 ;; Gross license change
5049 ;; Revision 1.6 2003/06/15 16:47:44 gilbert
5050 ;; OpenMCL patches by Patrik Nordebo
5052 ;; Revision 1.5 2003/03/16 17:48:43 gilbert
5053 ;; Kludged hyphenation and tex-mode in. It sort of works now. But consider
5054 ;; this still a hack.