Skip to content

Commit

Permalink
Add support for responsive images
Browse files Browse the repository at this point in the history
Add the srcset and sizes attribute to img tags whose class is
'img-responsive' or whose parent element class is 'figure' (default
Markdown behavior). Each image is made avaiable in three
version (roughly intended to match a mobile, 1x desktop and 2x/hi-dpi
desktop clients). Images are cached in a configurable directory.

In practice the above means that images in Markdown posts are made
responsive by default, and for Scribble posts the "img-responsive" class
can be added like so:

@image["img/some-image.gif" #:style "img-responsive"]

See example/.frogrc for documentation on added parameters. There are
also two additional parameters controlling resized image sizes and the
default size, see bottom of frog/params.rkt.

Depends on ImageMagick® for size identification and scaling.
  • Loading branch information
gerdint committed Dec 24, 2016
1 parent af40087 commit 6d2d032
Show file tree
Hide file tree
Showing 12 changed files with 339 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ example/tags/
example/2*
example/A-Non-Post-Scribble-Page*
example/img/posts/
example/img/resized/

# Emacs / DrRacket backups & auto save files
*~
Expand Down
21 changes: 20 additions & 1 deletion example/.frogrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
# Required: Should NOT end in trailing slash.
# -*- conf -*-

# Required: Should NOT end in trailing slash.
scheme/host = http://www.example.com

# A path prepended to URIs, including those specified here in .frogrc
Expand Down Expand Up @@ -117,3 +119,20 @@ python-executable = python
pygments-linenos? = true
## CSS class for the wrapping <div> tag (default: 'highlight').
pygments-cssclass = source

# Serve responsive images.
#
# Make use of the img srcset attribute to serve images inside elements
# of class "figure" (such as image referenced from Markdown) at three
# different sizes. Depends on having ImageMagick installed.
responsive-images? = true

# Subdirectory of where to put resized images. Defaults to "resized".
# The directory will be created but the parent "img" directory must exist.
#image-output-dir = resized

# Value of the img "sizes" attribute.
# Defaults to "(max-width: <image width>px) 100vw, <image width>px"
# If your blog's main column is narrower than the page width on wide
# clients you may want to have something like:
#image-sizes-attr = (max-width: <columnn width>) 100vw, <column width>
6 changes: 6 additions & 0 deletions example/_src/posts/2013-06-19-a-scribble-post.scrbl
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,9 @@ function foo() {
return 7;
}
}

@subsection[#:style 'unnumbered]{B SubSection}

A responsive big black image:

@image["img/800px-image.gif" #:style "img-responsive"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Title: A blog post featuring a big image
Date: 2016-08-12T02:43:56
Tags: images, responsive

The below `img` tag should come with _srcset_ and _sizes_ definitions:

![Image title](/img/800px-image.gif)
Binary file added example/img/1300px-image.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/img/600px-image.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added example/img/800px-image.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
146 changes: 145 additions & 1 deletion frog/enhance-body.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,161 @@
"html.rkt"
"params.rkt"
"pygments.rkt"
"xexpr-map.rkt")
"xexpr-map.rkt"
"verbosity.rkt"
"paths.rkt"
"responsive-images.rkt")

(provide enhance-body)

(module+ test (require rackunit))

(define (enhance-body xs)
(~> xs
responsive-images
syntax-highlight
add-racket-doc-links
auto-embed-tweets))

(define responsive-images
(let ([magick-notice-displayed? #f])
(λ (xs)
(define (remote-host url)
(url-host (string->url url)))
(define (do-it xs)
(for/list ([x xs])
(match x
[`(div ([class ,classes])
(img ,(list-no-order `[src ,url] attrs ...))
,content ...)
#:when (and (regexp-match #px"\\bfigure\\b" classes)
(not (remote-host url)))
(let ([sizes-attr (assq 'sizes attrs)])
`(div ([class ,classes])
(img ([class "img-responsive"] ; Add Bootstrap class
,@(make-responsive url (cond [sizes-attr => second]
[#t #f]))
,@(if sizes-attr
(remove sizes-attr attrs)
attrs)))
,@content))
]
;; xexpr-map?
[`(p () (img ,(list-no-order `[src ,url] `[class ,classes] attrs ...)))
#:when (and (regexp-match #px"\\bimg-responsive\\b" classes)
(not (remote-host url)))
`(p () (img ([class ,classes]
,@(make-responsive url #f) ; TODO honor custom sizes?
,@attrs)))]
[x x])))
(cond [(current-responsive-images?)
(if magick-available?
(do-it xs)
(begin
(unless magick-notice-displayed?
(prn1 "ImageMagick not found. Omitting img srcset attributes.")
(set! magick-notice-displayed? #t))
xs))]
[else xs]))))


(module+ test
(parameterize ([top example]
[current-responsive-images? #t]
[current-image-output-dir "resized"]
[current-image-sizes-attr #f]
[current-image-sizes '(320 600 1200)]
[current-image-default-size 600]
[current-verbosity 0])
(test-equal? "Remote images"
(responsive-images
'((div ((class "figure")) (img ((src "//somehost.com/img/file.jpg"))))))
;; Don't resize remote images. Or should we fetch it and resize it?
'((div ((class "figure")) (img ((src "//somehost.com/img/file.jpg"))))))
(when magick-available?
(test-equal? "Element-specific custom sizes attribute"
(responsive-images
'((div ([class "figure"])
(img ([src "/img/1x1.gif"]
[sizes "some-custom-size-spec"])))))
'((div ((class "figure"))
(img ([class "img-responsive"]
[src "/img/1x1.gif"]
[srcset "/img/1x1.gif 2w"]
[sizes "some-custom-size-spec"])))))
(test-equal? "Img with img-responsive class inside p tag"
(responsive-images
'((p () (img ([src "/img/1x1.gif"]
[alt ""]
[class "img-responsive among-others"]
[foo-attr "bar"])))))
'((p () (img ([class "img-responsive among-others"]
[src "/img/1x1.gif"]
[srcset "/img/1x1.gif 2w"]
[sizes "(max-width: 2px) 100vw, 2px"]
[alt ""]
[foo-attr "bar"])))))
(test-equal? "Image bigger than maximum size"
(responsive-images
'((div ([class "figure pull-right"])
(img ([src "/img/1300px-image.gif"] (alt "")))
(p ([class "caption"]) "some text"))))
`((div ((class "figure pull-right"))
(img ([class "img-responsive"]
[src "/img/resized/600/1300px-image.gif"]
[srcset
,(string-join
(for/list ([s (current-image-sizes)])
(format "/img/resized/~a/1300px-image.gif ~aw" s s))
", ")]
[sizes "(max-width: 1300px) 100vw, 1300px"]
(alt "")))
(p ((class "caption")) "some text"))))
(test-equal? "Image smaller than biggest size but bigger than smallest size"
(responsive-images
'((div ((class "figure"))
(img ((src "/img/800px-image.gif") (alt "")))
(p ((class "caption")) "some text"))))
`((div ((class "figure"))
(img ([class "img-responsive"]
(src ,(format "/img/resized/~a/800px-image.gif"
(current-image-default-size)))
(srcset
,(string-append
(string-join
(for/list ([s '(320 600)])
(format "/img/resized/~a/800px-image.gif ~aw" s s))
", ")
", /img/800px-image.gif 800w"))
(sizes "(max-width: 800px) 100vw, 800px")
(alt "")))
(p ((class "caption")) "some text"))))
(test-equal? "Image equal to a one of the sizes specified"
(responsive-images
'((div ((class "figure"))
(img ((src "/img/600px-image.gif") (alt "")))
(p ((class "caption")) "some text"))))
'((div ((class "figure"))
(img ([class "img-responsive"]
(src "/img/600px-image.gif")
(srcset "/img/resized/320/600px-image.gif 320w, /img/600px-image.gif 600w")
(sizes "(max-width: 600px) 100vw, 600px")
(alt "")))
(p ((class "caption")) "some text"))))
(test-equal? "Image smaller than smallest size"
(responsive-images
'((div ((class "figure"))
(img ((src "/img/1x1.gif") (alt ""))) ; Tiny image
(p ((class "caption")) "some text"))))
'((div ((class "figure"))
(img ([class "img-responsive"]
(src "/img/1x1.gif")
(srcset "/img/1x1.gif 2w")
(sizes "(max-width: 2px) 100vw, 2px")
(alt "")))
(p ((class "caption")) "some text"))))
(clean-resized-images))))

(define (syntax-highlight xs)
(for/list ([x xs])
(match x
Expand Down Expand Up @@ -131,6 +274,7 @@
"&hide_thread=true"))))
(define js (call/input-url oembed-url get-pure-port read-json))
(define html ('html js))

(cond [html (~>> (with-input-from-string html read-html-as-xexprs)
(append '(div ([class "embed-tweet"]))))]
[else x])]
Expand Down
13 changes: 10 additions & 3 deletions frog/frog.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@
"tags.rkt"
"util.rkt"
"verbosity.rkt"
"watch-dir.rkt")
"watch-dir.rkt"
"responsive-images.rkt")
(provide serve)

(module+ test
Expand Down Expand Up @@ -64,7 +65,12 @@
[output-dir "."]
[python-executable "python"]
[pygments-linenos? #t]
[pygments-cssclass "source"])
[pygments-cssclass "source"]
[responsive-images? #f]
[image-output-dir "resized"]
[image-sizes-attr #f]
[image-sizes '(320 768 1024)]
[image-default-size 768])
(define watch? #f)
(define port 3000)
(define root
Expand Down Expand Up @@ -351,7 +357,8 @@
(clean-post-output-files)
(clean-non-post-output-files)
(clean-tag-output-files)
(clean-serialized-posts))
(clean-serialized-posts)
(clean-resized-images))

;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;

Expand Down
5 changes: 5 additions & 0 deletions frog/params.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -40,3 +40,8 @@
(define current-python-executable (make-parameter "python"))
(define current-pygments-linenos? (make-parameter #t))
(define current-pygments-cssclass (make-parameter "source"))
(define current-responsive-images? (make-parameter #f))
(define current-image-output-dir (make-parameter "resized"))
(define current-image-sizes-attr (make-parameter #f))
(define current-image-sizes (make-parameter '(320 768 1024)))
(define current-image-default-size (make-parameter 768))
Loading

0 comments on commit 6d2d032

Please sign in to comment.