adamh-html_render 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/Manifest +8 -0
- data/README.rdoc +55 -0
- data/Rakefile +11 -0
- data/html_render.gemspec +38 -0
- data/lib/html_render/images.rb +47 -0
- data/lib/html_render/render_batches.rb +43 -0
- data/lib/html_render/renderers.rb +35 -0
- data/lib/html_render.rb +3 -0
- metadata +98 -0
data/Manifest
ADDED
data/README.rdoc
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
= html_render
|
2
|
+
|
3
|
+
html_render translates strings of HTML into PNG images.
|
4
|
+
|
5
|
+
The procedure is anything but magical. The process amounts to:
|
6
|
+
|
7
|
+
0. POST the HTML to a URL which translates HTML into PNG.
|
8
|
+
0. Read in the response.
|
9
|
+
|
10
|
+
The benefit comes when different URLs translate HTML into PNG differently.
|
11
|
+
With logic spanning many operating systems and web browser libraries, it is
|
12
|
+
possible to render several different PNGs for the same HTML. We can compare
|
13
|
+
the different PNGs to determine if our HTML is exposing a browser bug.
|
14
|
+
|
15
|
+
== Install
|
16
|
+
|
17
|
+
gem install adamh-html_render --source http://gems.github.com
|
18
|
+
|
19
|
+
== Example code
|
20
|
+
|
21
|
+
require 'rubygems'
|
22
|
+
require 'html_render'
|
23
|
+
|
24
|
+
ff3_renderer = HTMLRender::Renderers::HTTPRenderer.new('http://localhost:20558/ff3-linux')
|
25
|
+
ie6_renderer = HTMLRender::Renderers::HTTPRenderer.new('http://winxp-ie6.local:20558/ie')
|
26
|
+
|
27
|
+
html = '<html><body>Here is my HTML!</body></html>'
|
28
|
+
|
29
|
+
ff3_image = ff3_renderer.render(html)
|
30
|
+
ie6_image = ie6_renderer.render(html)
|
31
|
+
|
32
|
+
if ff3_image != ie6_image
|
33
|
+
puts 'Images differ! Differences recorded in diff.png'
|
34
|
+
diff = ff3_image.difference(ie6_image)
|
35
|
+
File.open('diff.png', 'w') { |f| f.write(diff.to_blob) }
|
36
|
+
end
|
37
|
+
|
38
|
+
== Usefulness
|
39
|
+
|
40
|
+
Because of different text-rendering engines, different browsers and operating
|
41
|
+
systems will almost always produce differing PNG files. The
|
42
|
+
+HTMLRender::Images::PNGImage.difference+ method produces a graphic with which
|
43
|
+
a person can quickly determine whether the changes are significant or not.
|
44
|
+
|
45
|
+
The intent behind this design--producing comparison images rather than using
|
46
|
+
fuzzy image matching--is for a manual verification process: presenting a user
|
47
|
+
with a list of rendered images so the user can quickly determine which images
|
48
|
+
are acceptable and which indicate a bug in the HTML (or, more commonly, a bug
|
49
|
+
in the browser which the HTML must work around). The engineering of such a
|
50
|
+
workflow is beyond the scope of this project.
|
51
|
+
|
52
|
+
== Dependencies
|
53
|
+
|
54
|
+
- httpclient >=2.1.4
|
55
|
+
- RMagick >= 2.9.1
|
data/Rakefile
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
require 'echoe'
|
4
|
+
|
5
|
+
Echoe.new('html_render', '0.0.1') do |p|
|
6
|
+
p.description = 'Make images from HTML strings'
|
7
|
+
p.url = 'http://adamhooper.com/eng'
|
8
|
+
p.author = 'Adam Hooper'
|
9
|
+
p.email = 'adam@adamhooper.com'
|
10
|
+
p.runtime_dependencies = [ 'httpclient >=2.1.4', 'rmagick >=2.9.1' ]
|
11
|
+
end
|
data/html_render.gemspec
ADDED
@@ -0,0 +1,38 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{html_render}
|
3
|
+
s.version = "0.0.1"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Adam Hooper"]
|
7
|
+
s.date = %q{2009-03-06}
|
8
|
+
s.description = %q{Make images from HTML strings}
|
9
|
+
s.email = %q{adam@adamhooper.com}
|
10
|
+
s.extra_rdoc_files = ["README.rdoc", "lib/html_render.rb", "lib/html_render/render_batches.rb", "lib/html_render/renderers.rb", "lib/html_render/images.rb"]
|
11
|
+
s.files = ["README.rdoc", "Rakefile", "html_render.gemspec", "lib/html_render.rb", "lib/html_render/render_batches.rb", "lib/html_render/renderers.rb", "lib/html_render/images.rb", "Manifest"]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.homepage = %q{http://adamhooper.com/eng}
|
14
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Html_render", "--main", "README.rdoc"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = %q{html_render}
|
17
|
+
s.rubygems_version = %q{1.2.0}
|
18
|
+
s.summary = %q{Make images from HTML strings}
|
19
|
+
|
20
|
+
if s.respond_to? :specification_version then
|
21
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
22
|
+
s.specification_version = 2
|
23
|
+
|
24
|
+
if current_version >= 3 then
|
25
|
+
s.add_runtime_dependency(%q<httpclient>, [">= 2.1.4"])
|
26
|
+
s.add_runtime_dependency(%q<rmagick>, [">= 2.9.1"])
|
27
|
+
s.add_development_dependency(%q<echoe>, [">= 0"])
|
28
|
+
else
|
29
|
+
s.add_dependency(%q<httpclient>, [">= 2.1.4"])
|
30
|
+
s.add_dependency(%q<rmagick>, [">= 2.9.1"])
|
31
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
32
|
+
end
|
33
|
+
else
|
34
|
+
s.add_dependency(%q<httpclient>, [">= 2.1.4"])
|
35
|
+
s.add_dependency(%q<rmagick>, [">= 2.9.1"])
|
36
|
+
s.add_dependency(%q<echoe>, [">= 0"])
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'RMagick'
|
2
|
+
require 'tempfile'
|
3
|
+
|
4
|
+
module HTMLRender; end
|
5
|
+
|
6
|
+
module HTMLRender::Images
|
7
|
+
class Base
|
8
|
+
def ==(other)
|
9
|
+
return false unless self.class === other
|
10
|
+
(self <=> other) == 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def difference(other)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class PNGImage < Base
|
19
|
+
attr_reader :png
|
20
|
+
|
21
|
+
def initialize(data)
|
22
|
+
@png = Magick::Image.from_blob(data)[0]
|
23
|
+
end
|
24
|
+
|
25
|
+
def <=>(other)
|
26
|
+
(png <=> other.png) == 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def difference(other)
|
30
|
+
raise NotImplementedError unless PNGImage === other
|
31
|
+
|
32
|
+
rows = [ png.rows, other.png.rows ].max
|
33
|
+
columns = [ png.columns, other.png.columns ].max
|
34
|
+
|
35
|
+
image = Magick::Image.new(columns, rows) do |info|
|
36
|
+
info.format = 'png'
|
37
|
+
end
|
38
|
+
image.background_color = 'black'
|
39
|
+
image.composite!(png, 0, 0, Magick::OverCompositeOp)
|
40
|
+
image.composite!(other.png, 0, 0, Magick::DifferenceCompositeOp)
|
41
|
+
end
|
42
|
+
|
43
|
+
def to_blob
|
44
|
+
png.to_blob
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'html_render/renderers'
|
2
|
+
|
3
|
+
module HTMLRender; end
|
4
|
+
module HTMLRender::RenderBatches
|
5
|
+
class HTTPRenderBatch
|
6
|
+
attr_reader :servers
|
7
|
+
|
8
|
+
# +servers+: Hash of (String) key to (String) URL to pass to
|
9
|
+
# HTTPRenderer
|
10
|
+
def initialize(servers)
|
11
|
+
@servers = servers
|
12
|
+
end
|
13
|
+
|
14
|
+
# +html+: HTML to render
|
15
|
+
# +directory+: Directory in which to dump PNG files (one per server)
|
16
|
+
def render_html_to_directory(html, directory)
|
17
|
+
threads = create_threads(html, directory)
|
18
|
+
threads.each { |t| t.join }
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def create_threads(html, dir)
|
24
|
+
threads = []
|
25
|
+
|
26
|
+
servers.each do |key, url|
|
27
|
+
threads << create_thread(html, url, File.join(dir, "#{key}.png"))
|
28
|
+
end
|
29
|
+
|
30
|
+
threads
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_thread(html, url, filename)
|
34
|
+
Thread.new(html, url, filename) do |html, url, filename|
|
35
|
+
renderer = HTMLRender::Renderers::HTTPRenderer.new(url)
|
36
|
+
image = renderer.render(html)
|
37
|
+
File.open(filename, 'w') do |f|
|
38
|
+
f.write(image.to_blob)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'httpclient'
|
2
|
+
|
3
|
+
require 'html_render/images'
|
4
|
+
|
5
|
+
module HTMLRender; end
|
6
|
+
module HTMLRender::Renderers
|
7
|
+
class Base
|
8
|
+
def render(html)
|
9
|
+
raise NotImplementedError
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Renders using an HTTP server built for the very purpose.
|
14
|
+
#
|
15
|
+
# One should POST the HTML to the given URL, and the server should
|
16
|
+
# respond with a PNG image.
|
17
|
+
class HTTPRenderer < Base
|
18
|
+
attr_accessor :url
|
19
|
+
|
20
|
+
def initialize(url)
|
21
|
+
@url = url
|
22
|
+
end
|
23
|
+
|
24
|
+
def render(html)
|
25
|
+
client = HTTPClient.new
|
26
|
+
|
27
|
+
response = client.post(url, html)
|
28
|
+
if response.status != 200
|
29
|
+
raise Exception.new("Unexpected HTTP server response from #{url}: #{response.inspect}")
|
30
|
+
end
|
31
|
+
|
32
|
+
HTMLRender::Images::PNGImage.new(response.content)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/html_render.rb
ADDED
metadata
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: adamh-html_render
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Hooper
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-06 00:00:00 -08:00
|
13
|
+
default_executable:
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: httpclient
|
17
|
+
type: :runtime
|
18
|
+
version_requirement:
|
19
|
+
version_requirements: !ruby/object:Gem::Requirement
|
20
|
+
requirements:
|
21
|
+
- - ">="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 2.1.4
|
24
|
+
version:
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rmagick
|
27
|
+
type: :runtime
|
28
|
+
version_requirement:
|
29
|
+
version_requirements: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 2.9.1
|
34
|
+
version:
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: echoe
|
37
|
+
type: :runtime
|
38
|
+
version_requirement:
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: "0"
|
44
|
+
version:
|
45
|
+
description: Make images from HTML strings
|
46
|
+
email: adam@adamhooper.com
|
47
|
+
executables: []
|
48
|
+
|
49
|
+
extensions: []
|
50
|
+
|
51
|
+
extra_rdoc_files:
|
52
|
+
- README.rdoc
|
53
|
+
- lib/html_render.rb
|
54
|
+
- lib/html_render/render_batches.rb
|
55
|
+
- lib/html_render/renderers.rb
|
56
|
+
- lib/html_render/images.rb
|
57
|
+
files:
|
58
|
+
- README.rdoc
|
59
|
+
- Rakefile
|
60
|
+
- html_render.gemspec
|
61
|
+
- lib/html_render.rb
|
62
|
+
- lib/html_render/render_batches.rb
|
63
|
+
- lib/html_render/renderers.rb
|
64
|
+
- lib/html_render/images.rb
|
65
|
+
- Manifest
|
66
|
+
has_rdoc: true
|
67
|
+
homepage: http://adamhooper.com/eng
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options:
|
70
|
+
- --line-numbers
|
71
|
+
- --inline-source
|
72
|
+
- --title
|
73
|
+
- Html_render
|
74
|
+
- --main
|
75
|
+
- README.rdoc
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: "0"
|
83
|
+
version:
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: "1.2"
|
89
|
+
version:
|
90
|
+
requirements: []
|
91
|
+
|
92
|
+
rubyforge_project: html_render
|
93
|
+
rubygems_version: 1.2.0
|
94
|
+
signing_key:
|
95
|
+
specification_version: 2
|
96
|
+
summary: Make images from HTML strings
|
97
|
+
test_files: []
|
98
|
+
|