, except for a trailing table ;; when `special-last?' is #t (let loop ([f (flow-paragraphs p)][inline? start-inline?]) (cond [(null? f) null] [(and (table? (car f)) (or (not special-last?) (not (null? (cdr f))))) (cons `(p ,@(render-block (car f) part ri inline?)) (loop (cdr f) #f))] [else (append (render-block (car f) part ri inline?) (loop (cdr f) #f))]))) (define/override (render-flow p part ri start-inline?) (render-flow* p part ri start-inline? #t)) (define/override (render-paragraph p part ri) `((p ,@(if (styled-paragraph? p) `(((class ,(styled-paragraph-style p)))) null) ,@(super render-paragraph p part ri)))) (define/override (render-element e part ri) (cond [(hover-element? e) `((span ((title ,(hover-element-text e))) ,@(render-plain-element e part ri)))] [(target-element? e) `((a ((name ,(format "~a" (anchor-name (tag-key (target-element-tag e) ri)))))) ,@(render-plain-element e part ri))] [(and (link-element? e) (not (current-no-links))) (parameterize ([current-no-links #t]) (let ([dest (resolve-get part ri (link-element-tag e))]) (if dest `((a ((href ,(format "~a~a~a" (from-root (relative->path (dest-path dest)) (get-dest-directory)) (if (dest-page? dest) "" "#") (if (dest-page? dest) "" (anchor-name (dest-anchor dest))))) ,@(if (string? (element-style e)) `((class ,(element-style e))) null)) ,@(if (null? (element-content e)) (render-content (strip-aux (dest-title dest)) part ri) (render-content (element-content e) part ri)))) (begin (when #f (fprintf (current-error-port) "Undefined link: ~s~n" (tag-key (link-element-tag e) ri))) `((font ((class "badlink")) ,@(if (null? (element-content e)) `(,(format "~s" (tag-key (link-element-tag e) ri))) (render-plain-element e part ri))))))))] [else (render-plain-element e part ri)])) (define/private (render-plain-element e part ri) (let ([style (and (element? e) (element-style e))]) (cond [(symbol? style) (case style [(italic) `((i ,@(super render-element e part ri)))] [(bold) `((b ,@(super render-element e part ri)))] [(tt) `((span ([class "stt"]) ,@(super render-element e part ri)))] [(no-break) `((span ([class "nobreak"]) ,@(super render-element e part ri)))] [(sf) `((b (font ([size "-1"][face "Helvetica"]) ,@(super render-element e part ri))))] [(subscript) `((sub ,@(super render-element e part ri)))] [(superscript) `((sup ,@(super render-element e part ri)))] [(hspace) `((span ([class "hspace"]) ,@(let ([str (content->string (element-content e))]) (map (lambda (c) 'nbsp) (string->list str)))))] [(newline) `((br))] [else (error 'html-render "unrecognized style symbol: ~e" style)])] [(string? style) `((span ([class ,style]) ,@(super render-element e part ri)))] [(and (pair? style) (or (eq? (car style) 'bg-color) (eq? (car style) 'color))) (unless (and (list? style) (or (and (= 4 (length style)) (andmap byte? (cdr style))) (and (= 2 (length style)) (member (cadr style) '("white" "black" "red" "green" "blue" "cyan" "magenta" "yellow"))))) (error 'render-font "bad color style: ~e" style)) `((font ((style ,(format "~acolor: ~a" (if (eq? (car style) 'bg-color) "background-" "") (if (= 2 (length style)) (cadr style) (apply string-append "#" (map (lambda (v) (let ([s (format "0~x" v)]) (substring s (- (string-length s) 2)))) (cdr style))))))) ,@(super render-element e part ri)))] [(target-url? style) (if (current-no-links) (super render-element e part ri) (parameterize ([current-no-links #t]) `((a ((href ,(let ([addr (target-url-addr style)]) (if (path? addr) (from-root addr (get-dest-directory)) addr))) ,@(if (string? (target-url-style style)) `((class ,(target-url-style style))) null)) ,@(super render-element e part ri)))))] [(url-anchor? style) `((a ((name ,(url-anchor-name style))) ,@(super render-element e part ri)))] [(image-file? style) (let* ([src (main-collects-relative->path (image-file-path style))] [scale (image-file-scale style)] [sz (if (= 1.0 scale) null ;; Try to extract file size: (call-with-input-file* src (lambda (in) (if (regexp-try-match #px#"^\211PNG.{12}" in) (let ([w (read-bytes 4 in)] [h (read-bytes 4 in)] [to-num (lambda (s) (number->string (inexact->exact (floor (* scale (integer-bytes->integer s #f #t))))))]) `((width ,(to-num w)) (height ,(to-num h)))) null))))]) `((img ((src ,(install-file src))) ,@sz)))] [else (super render-element e part ri)]))) (define/override (render-table t part ri need-inline?) (define index? (eq? 'index (table-style t))) `(,@(if index? `(,search-script ,search-field) '()) (table ((cellspacing "0") ,@(if need-inline? '((style "display: inline; vertical-align: top;")) null) ,@(case (table-style t) [(boxed) '((class "boxed"))] [(centered) '((align "center"))] [(at-right) '((align "right"))] [(at-left) '((align "left"))] [else null]) ,@(let ([a (and (list? (table-style t)) (assoc 'style (table-style t)))]) (if (and a (string? (cadr a))) `((class ,(cadr a))) null)) ,@(if (string? (table-style t)) `((class ,(table-style t))) null)) ,@(map (lambda (flows style) `(tr (,@(if style `((class ,style)) null)) ,@(let loop ([ds flows] [as (cdr (or (and (list? (table-style t)) (assoc 'alignment (or (table-style t) null))) (cons #f (map (lambda (x) #f) flows))))] [vas (cdr (or (and (list? (table-style t)) (assoc 'valignment (or (table-style t) null))) (cons #f (map (lambda (x) #f) flows))))]) (if (null? ds) null (if (eq? (car ds) 'cont) (loop (cdr ds) (cdr as) (cdr vas)) (let ([d (car ds)] [a (car as)] [va (car vas)]) (cons `(td (,@(case a [(#f) null] [(right) '((align "right"))] [(center) '((align "center"))] [(left) '((align "left"))]) ,@(case va [(#f) null] [(top) '((valign "top"))] [(baseline) '((valign "baseline"))] [(bottom) '((valign "bottom"))]) ,@(if (and (pair? (cdr ds)) (eq? 'cont (cadr ds))) `((colspan ,(number->string (let loop ([n 2] [ds (cddr ds)]) (cond [(null? ds) n] [(eq? 'cont (car ds)) (loop (+ n 1) (cdr ds))] [else n]))))) null)) ,@(render-flow d part ri #f)) (loop (cdr ds) (cdr as) (cdr vas))))))))) (table-flowss t) (cdr (or (and (list? (table-style t)) (assoc 'row-styles (or (table-style t) null))) (cons #f (map (lambda (x) #f) (table-flowss t))))))))) (define/override (render-blockquote t part ri) `((blockquote ,@(if (string? (blockquote-style t)) `(((class ,(blockquote-style t)))) null) ,@(apply append (map (lambda (i) (render-block i part ri #f)) (blockquote-paragraphs t)))))) (define/override (render-itemization t part ri) `((ul ,@(if (and (styled-itemization? t) (string? (styled-itemization-style t))) `(((class ,(styled-itemization-style t)))) null) ,@(map (lambda (flow) `(li ,@(render-flow flow part ri #t))) (itemization-flows t))))) (define/override (render-other i part ri) (cond [(string? i) (let ([m (and (extra-breaking?) (regexp-match-positions #rx"[-:/+]" i))]) (if m (list* (substring i 0 (cdar m)) ;; Most browsers wrap after a hyphen. The ;; one that doesn't, Firefox, pays attention ;; to wbr. Some browsers ignore wbr, but ;; at least they don't do strange things with it. (if (equal? #\- (string-ref i (caar m))) '(wbr) `(span ((class "mywbr")) " ")) (render-other (substring i (cdar m)) part ri)) (ascii-ize i)))] [(eq? i 'mdash) `(" " ndash " ")] [(symbol? i) (list i)] [else (list (format "~s" i))])) (define/private (ascii-ize s) (let ([m (regexp-match-positions #rx"[^\u01-\u7E]" s)]) (if m (append (ascii-ize (substring s 0 (caar m))) (list (char->integer (string-ref s (caar m)))) (ascii-ize (substring s (cdar m)))) (list s)))) ;; ---------------------------------------- (super-new))) ;; ---------------------------------------- ;; multi-file output (define (render-multi-mixin %) (class % (inherit render-one render-one-part render-content part-whole-page? format-number) (inherit-field report-output?) (define/override (get-suffix) #"") (define/override (get-dest-directory) (or (build-path (or (super get-dest-directory) (current-directory)) (current-subdirectory)) (super get-dest-directory))) (define/override (derive-filename d) (let ([fn (format "~a.html" (regexp-replace* "[^-a-zA-Z0-9_=]" (let ([s (cadr (car (part-tags d)))]) (if (string? s) s (if (part-title-content d) (content->string (part-title-content d)) ;; last-ditch effort to make up a unique name: (format "???~a" (eq-hash-code d))))) "_"))]) (when ((string-length fn) . >= . 48) (error "file name too long (need a tag):" fn)) fn)) (define/override (collect ds fns) (super collect ds (map (lambda (fn) (build-path fn "index.html")) fns))) (define/override (current-part-whole-page? d) ((collecting-sub) . <= . 2)) (define/override (collect-part d parent ci number) (let ([prev-sub (collecting-sub)]) (parameterize ([collecting-sub (if (toc-part? d) 1 (add1 prev-sub))]) (if (= 1 prev-sub) (let ([filename (derive-filename d)]) (parameterize ([current-output-file (build-path (path-only (current-output-file)) filename)]) (super collect-part d parent ci number))) (super collect-part d parent ci number))))) (define/override (render ds fns ri) (map (lambda (d fn) (when report-output? (printf " [Output to ~a/index.html]\n" fn)) (unless (directory-exists? fn) (make-directory fn)) (parameterize ([current-subdirectory (file-name-from-path fn)]) (let ([fn (build-path fn "index.html")]) (with-output-to-file fn #:exists 'truncate/replace (lambda () (render-one d ri fn)))))) ds fns)) (define/override (nearly-top? d ri top) (eq? top (collected-info-parent (part-collected-info d ri)))) (define/override (get-onthispage-label) `((div ((class "tocsubtitle")) "On this page:"))) (define/override (toc-wrap p) (list p)) (inherit render-table render-paragraph) (define/override (render-part d ri) (parameterize ([current-version (if (and (versioned-part? d) (versioned-part-version d)) (versioned-part-version d) (current-version))]) (let ([number (collected-info-number (part-collected-info d ri))]) (cond [(and (not (on-separate-page)) (or (= 1 (length number)) (next-separate-page))) ;; Render as just a link, and put the actual ;; content in a new file: (let* ([filename (derive-filename d)] [full-path (build-path (path-only (current-output-file)) filename)]) (parameterize ([on-separate-page #t]) (with-output-to-file full-path #:exists 'truncate/replace (lambda () (render-one-part d ri full-path number))) null))] [else (let ([sep? (on-separate-page)]) (parameterize ([next-separate-page (toc-part? d)] [on-separate-page #f]) ;; Normal section render (super render-part d ri)))])))) (super-new))) ;; ---------------------------------------- ;; utils (define (from-root p d) (if (not d) p (let ([e-d (explode (path->complete-path d (current-directory)))] [e-p (explode (path->complete-path p (current-directory)))]) (let loop ([e-d e-d] [e-p e-p]) (cond [(null? e-d) (let loop ([e-p e-p]) (cond [(null? e-p) "/"] [(null? (cdr e-p)) (car e-p)] [(eq? 'same (car e-p)) (loop (cdr e-p))] [(eq? 'up (car e-p)) (string-append "../" (loop (cdr e-p)))] [else (string-append (car e-p) "/" (loop (cdr e-p)))]))] [(equal? (car e-d) (car e-p)) (loop (cdr e-d) (cdr e-p))] [(eq? 'same (car e-d)) (loop (cdr e-d) e-p)] [(eq? 'same (car e-p)) (loop e-d (cdr e-p))] [else (string-append (apply string-append (map (lambda (x) "../") e-d)) (loop null e-p))]))))) (define (explode p) (reverse (let loop ([p p]) (let-values ([(base name dir?) (split-path p)]) (let ([name (if base (if (path? name) (path-element->string name) name) name)]) (if (path? base) (cons name (loop base)) (list name))))))))