Skip to content

Commit 64b1c2a

Browse files
committed
Add support for responsive images
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.
1 parent af40087 commit 64b1c2a

12 files changed

Lines changed: 339 additions & 6 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ example/tags/
1313
example/2*
1414
example/A-Non-Post-Scribble-Page*
1515
example/img/posts/
16+
example/img/resized/
1617

1718
# Emacs / DrRacket backups & auto save files
1819
*~

example/.frogrc

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# Required: Should NOT end in trailing slash.
1+
# -*- conf -*-
2+
3+
# Required: Should NOT end in trailing slash.
24
scheme/host = http://www.example.com
35

46
# A path prepended to URIs, including those specified here in .frogrc
@@ -117,3 +119,20 @@ python-executable = python
117119
pygments-linenos? = true
118120
## CSS class for the wrapping <div> tag (default: 'highlight').
119121
pygments-cssclass = source
122+
123+
# Serve responsive images.
124+
#
125+
# Make use of the img srcset attribute to serve images inside elements
126+
# of class "figure" (such as image referenced from Markdown) at three
127+
# different sizes. Depends on having ImageMagick installed.
128+
responsive-images? = true
129+
130+
# Subdirectory of where to put resized images. Defaults to "resized".
131+
# The directory will be created but the parent "img" directory must exist.
132+
#image-output-dir = resized
133+
134+
# Value of the img "sizes" attribute.
135+
# Defaults to "(max-width: <image width>px) 100vw, <image width>px"
136+
# If your blog's main column is narrower than the page width on wide
137+
# clients you may want to have something like:
138+
#image-sizes-attr = (max-width: <columnn width>) 100vw, <column width>

example/_src/posts/2013-06-19-a-scribble-post.scrbl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,3 +110,9 @@ function foo() {
110110
return 7;
111111
}
112112
}
113+
114+
@subsection[#:style 'unnumbered]{B SubSection}
115+
116+
A responsive big black image:
117+
118+
@image["img/800px-image.gif" #:style "img-responsive"]
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
Title: A blog post featuring a big image
2+
Date: 2016-08-12T02:43:56
3+
Tags: images, responsive
4+
5+
The below `img` tag should come with _srcset_ and _sizes_ definitions:
6+
7+
![Image title](/img/800px-image.gif)

example/img/1300px-image.gif

2.27 KB
Loading

example/img/600px-image.gif

985 Bytes
Loading

example/img/800px-image.gif

1.32 KB
Loading

frog/enhance-body.rkt

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,18 +13,161 @@
1313
"html.rkt"
1414
"params.rkt"
1515
"pygments.rkt"
16-
"xexpr-map.rkt")
16+
"xexpr-map.rkt"
17+
"verbosity.rkt"
18+
"paths.rkt"
19+
"responsive-images.rkt")
1720

1821
(provide enhance-body)
1922

2023
(module+ test (require rackunit))
2124

2225
(define (enhance-body xs)
2326
(~> xs
27+
responsive-images
2428
syntax-highlight
2529
add-racket-doc-links
2630
auto-embed-tweets))
2731

32+
(define responsive-images
33+
(let ([magick-notice-displayed? #f])
34+
(λ (xs)
35+
(define (remote-host url)
36+
(url-host (string->url url)))
37+
(define (do-it xs)
38+
(for/list ([x xs])
39+
(match x
40+
[`(div ([class ,classes])
41+
(img ,(list-no-order `[src ,url] attrs ...))
42+
,content ...)
43+
#:when (and (regexp-match #px"\\bfigure\\b" classes)
44+
(not (remote-host url)))
45+
(let ([sizes-attr (assq 'sizes attrs)])
46+
`(div ([class ,classes])
47+
(img ([class "img-responsive"] ; Add Bootstrap class
48+
,@(make-responsive url (cond [sizes-attr => second]
49+
[#t #f]))
50+
,@(if sizes-attr
51+
(remove sizes-attr attrs)
52+
attrs)))
53+
,@content))
54+
]
55+
;; xexpr-map?
56+
[`(p () (img ,(list-no-order `[src ,url] `[class ,classes] attrs ...)))
57+
#:when (and (regexp-match #px"\\bimg-responsive\\b" classes)
58+
(not (remote-host url)))
59+
`(p () (img ([class ,classes]
60+
,@(make-responsive url #f) ; TODO honor custom sizes?
61+
,@attrs)))]
62+
[x x])))
63+
(cond [(current-responsive-images?)
64+
(if magick-available?
65+
(do-it xs)
66+
(begin
67+
(unless magick-notice-displayed?
68+
(prn1 "ImageMagick not found. Omitting img srcset attributes.")
69+
(set! magick-notice-displayed? #t))
70+
xs))]
71+
[else xs]))))
72+
73+
74+
(module+ test
75+
(parameterize ([top example]
76+
[current-responsive-images? #t]
77+
[current-image-output-dir "resized"]
78+
[current-image-sizes-attr #f]
79+
[current-image-sizes '(320 600 1200)]
80+
[current-image-default-size 600]
81+
[current-verbosity 0])
82+
(test-equal? "Remote images"
83+
(responsive-images
84+
'((div ((class "figure")) (img ((src "//somehost.com/img/file.jpg"))))))
85+
;; Don't resize remote images. Or should we fetch it and resize it?
86+
'((div ((class "figure")) (img ((src "//somehost.com/img/file.jpg"))))))
87+
(when magick-available?
88+
(test-equal? "Element-specific custom sizes attribute"
89+
(responsive-images
90+
'((div ([class "figure"])
91+
(img ([src "/img/1x1.gif"]
92+
[sizes "some-custom-size-spec"])))))
93+
'((div ((class "figure"))
94+
(img ([class "img-responsive"]
95+
[src "/img/1x1.gif"]
96+
[srcset "/img/1x1.gif 2w"]
97+
[sizes "some-custom-size-spec"])))))
98+
(test-equal? "Img with img-responsive class inside p tag"
99+
(responsive-images
100+
'((p () (img ([src "/img/1x1.gif"]
101+
[alt ""]
102+
[class "img-responsive among-others"]
103+
[foo-attr "bar"])))))
104+
'((p () (img ([class "img-responsive among-others"]
105+
[src "/img/1x1.gif"]
106+
[srcset "/img/1x1.gif 2w"]
107+
[sizes "(max-width: 2px) 100vw, 2px"]
108+
[alt ""]
109+
[foo-attr "bar"])))))
110+
(test-equal? "Image bigger than maximum size"
111+
(responsive-images
112+
'((div ([class "figure pull-right"])
113+
(img ([src "/img/1300px-image.gif"] (alt "")))
114+
(p ([class "caption"]) "some text"))))
115+
`((div ((class "figure pull-right"))
116+
(img ([class "img-responsive"]
117+
[src "/img/resized/600/1300px-image.gif"]
118+
[srcset
119+
,(string-join
120+
(for/list ([s (current-image-sizes)])
121+
(format "/img/resized/~a/1300px-image.gif ~aw" s s))
122+
", ")]
123+
[sizes "(max-width: 1300px) 100vw, 1300px"]
124+
(alt "")))
125+
(p ((class "caption")) "some text"))))
126+
(test-equal? "Image smaller than biggest size but bigger than smallest size"
127+
(responsive-images
128+
'((div ((class "figure"))
129+
(img ((src "/img/800px-image.gif") (alt "")))
130+
(p ((class "caption")) "some text"))))
131+
`((div ((class "figure"))
132+
(img ([class "img-responsive"]
133+
(src ,(format "/img/resized/~a/800px-image.gif"
134+
(current-image-default-size)))
135+
(srcset
136+
,(string-append
137+
(string-join
138+
(for/list ([s '(320 600)])
139+
(format "/img/resized/~a/800px-image.gif ~aw" s s))
140+
", ")
141+
", /img/800px-image.gif 800w"))
142+
(sizes "(max-width: 800px) 100vw, 800px")
143+
(alt "")))
144+
(p ((class "caption")) "some text"))))
145+
(test-equal? "Image equal to a one of the sizes specified"
146+
(responsive-images
147+
'((div ((class "figure"))
148+
(img ((src "/img/600px-image.gif") (alt "")))
149+
(p ((class "caption")) "some text"))))
150+
'((div ((class "figure"))
151+
(img ([class "img-responsive"]
152+
(src "/img/600px-image.gif")
153+
(srcset "/img/resized/320/600px-image.gif 320w, /img/600px-image.gif 600w")
154+
(sizes "(max-width: 600px) 100vw, 600px")
155+
(alt "")))
156+
(p ((class "caption")) "some text"))))
157+
(test-equal? "Image smaller than smallest size"
158+
(responsive-images
159+
'((div ((class "figure"))
160+
(img ((src "/img/1x1.gif") (alt ""))) ; Tiny image
161+
(p ((class "caption")) "some text"))))
162+
'((div ((class "figure"))
163+
(img ([class "img-responsive"]
164+
(src "/img/1x1.gif")
165+
(srcset "/img/1x1.gif 2w")
166+
(sizes "(max-width: 2px) 100vw, 2px")
167+
(alt "")))
168+
(p ((class "caption")) "some text"))))
169+
(clean-resized-images))))
170+
28171
(define (syntax-highlight xs)
29172
(for/list ([x xs])
30173
(match x
@@ -131,6 +274,7 @@
131274
"&hide_thread=true"))))
132275
(define js (call/input-url oembed-url get-pure-port read-json))
133276
(define html ('html js))
277+
134278
(cond [html (~>> (with-input-from-string html read-html-as-xexprs)
135279
(append '(div ([class "embed-tweet"]))))]
136280
[else x])]

frog/frog.rkt

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"tags.rkt"
2626
"util.rkt"
2727
"verbosity.rkt"
28-
"watch-dir.rkt")
28+
"watch-dir.rkt"
29+
"responsive-images.rkt")
2930
(provide serve)
3031

3132
(module+ test
@@ -64,7 +65,12 @@
6465
[output-dir "."]
6566
[python-executable "python"]
6667
[pygments-linenos? #t]
67-
[pygments-cssclass "source"])
68+
[pygments-cssclass "source"]
69+
[responsive-images? #f]
70+
[image-output-dir "resized"]
71+
[image-sizes-attr #f]
72+
[image-sizes '(320 768 1024)]
73+
[image-default-size 768])
6874
(define watch? #f)
6975
(define port 3000)
7076
(define root
@@ -351,7 +357,8 @@
351357
(clean-post-output-files)
352358
(clean-non-post-output-files)
353359
(clean-tag-output-files)
354-
(clean-serialized-posts))
360+
(clean-serialized-posts)
361+
(clean-resized-images))
355362

356363
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
357364

frog/params.rkt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,8 @@
4040
(define current-python-executable (make-parameter "python"))
4141
(define current-pygments-linenos? (make-parameter #t))
4242
(define current-pygments-cssclass (make-parameter "source"))
43+
(define current-responsive-images? (make-parameter #f))
44+
(define current-image-output-dir (make-parameter "resized"))
45+
(define current-image-sizes-attr (make-parameter #f))
46+
(define current-image-sizes (make-parameter '(320 768 1024)))
47+
(define current-image-default-size (make-parameter 768))

0 commit comments

Comments
 (0)