djatoka 0.2.2 → 0.2.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +2 -0
- data/Gemfile.lock +9 -2
- data/README.rdoc +42 -3
- data/djatoka.gemspec +7 -5
- data/lib/djatoka/iiif_request.rb +163 -0
- data/lib/djatoka/metadata.rb +88 -3
- data/lib/djatoka/resolver.rb +10 -0
- data/lib/djatoka.rb +4 -0
- data/test/helper.rb +2 -1
- data/test/test_iiif_request.rb +173 -0
- data/test/test_metadata.rb +81 -6
- data/test/test_resolver.rb +20 -0
- metadata +53 -39
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 11aaf1e80818a81069ad5935756e293a80b87dbf
|
4
|
+
data.tar.gz: 850d1b033f7c80a5ac496e1fcfa3902ad1040bb8
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: ac8a7b99068dd81346499e49302d00a5bced11ee481744f8a3698e8f4ff66f93adcffbe6e8a2fa741c1a4bc96e012e518088116f576f4232a4b808f39c996288
|
7
|
+
data.tar.gz: 3e0231731a8cbe8154d37895994a2e8b0d097eaae00a40866dd3bf15a5e64302bda280e16e7991e05a1de3e258e938c2d1b949a8fbff314589a21cb1e2444373
|
data/.gitignore
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
djatoka (0.2.
|
4
|
+
djatoka (0.2.2)
|
5
5
|
addressable
|
6
6
|
hashie
|
7
7
|
json
|
8
|
+
mime-types
|
8
9
|
trollop
|
9
10
|
|
10
11
|
GEM
|
@@ -13,10 +14,12 @@ GEM
|
|
13
14
|
activesupport (3.2.13)
|
14
15
|
i18n (= 0.6.1)
|
15
16
|
multi_json (~> 1.0)
|
16
|
-
addressable (2.3.
|
17
|
+
addressable (2.3.4)
|
17
18
|
bourne (1.4.0)
|
18
19
|
mocha (~> 0.13.2)
|
19
20
|
coderay (1.0.9)
|
21
|
+
equivalent-xml (0.3.0)
|
22
|
+
nokogiri (>= 1.4.3)
|
20
23
|
fakeweb (1.3.0)
|
21
24
|
ffi (1.6.0-java)
|
22
25
|
hashie (2.0.3)
|
@@ -25,9 +28,12 @@ GEM
|
|
25
28
|
json (1.7.7-java)
|
26
29
|
metaclass (0.0.1)
|
27
30
|
method_source (0.8.1)
|
31
|
+
mime-types (1.22)
|
28
32
|
mocha (0.13.3)
|
29
33
|
metaclass (~> 0.0.1)
|
30
34
|
multi_json (1.7.2)
|
35
|
+
nokogiri (1.5.9)
|
36
|
+
nokogiri (1.5.9-java)
|
31
37
|
pry (0.9.12)
|
32
38
|
coderay (~> 1.0.5)
|
33
39
|
method_source (~> 0.8)
|
@@ -59,6 +65,7 @@ PLATFORMS
|
|
59
65
|
|
60
66
|
DEPENDENCIES
|
61
67
|
djatoka!
|
68
|
+
equivalent-xml
|
62
69
|
fakeweb
|
63
70
|
mocha
|
64
71
|
pry
|
data/README.rdoc
CHANGED
@@ -90,6 +90,46 @@ Tested with the following Ruby versions:
|
|
90
90
|
- 1.8.7-p302
|
91
91
|
- 1.9.2-p0
|
92
92
|
|
93
|
+
== IIIF Support
|
94
|
+
This gem can translate parameters from {International Image Interoperability Framework (IIIF)}[http://www-sul.stanford.edu/iiif/image-api/] requests into Djatoka requests. It follows the same pattern as creating a Djatoka::Region
|
95
|
+
|
96
|
+
=== IIIF Example
|
97
|
+
# Create a resolver object with a URL.
|
98
|
+
resolver = Djatoka::Resolver.new('http://african.lanl.gov/adore-djatoka/resolver')
|
99
|
+
|
100
|
+
# A known good identifier
|
101
|
+
identifier = 'info:lanl-repo/ds/5aa182c2-c092-4596-af6e-e95d2e263de3'
|
102
|
+
|
103
|
+
# IIIF Image Request
|
104
|
+
# Create a IiifRequest with the resolver and id
|
105
|
+
iiif_request = Djatoka::IiifRequest.new(resolver, identifier)
|
106
|
+
|
107
|
+
# Set IIIF parameters and create a Djatoka::Region
|
108
|
+
# Note: All IIIF parameters are required before creating a Djatoka::Region
|
109
|
+
djatoka_region = iiif_request.region('full').size('full').rotation('0').quality('native').format('jpg').djatoka_region
|
110
|
+
|
111
|
+
# Use the Djatoka::Region as normal
|
112
|
+
djatoka_region.url
|
113
|
+
|
114
|
+
# IIIF Info Request (Metadata)
|
115
|
+
# First, create a Djatoka::Metadata object and perform the request to the server for metadata.
|
116
|
+
metadata = resolver.metadata(identifier).perform
|
117
|
+
|
118
|
+
# Create a IIIF Info json response string
|
119
|
+
# Set values to optional fields by passing in a block and calling setters on the yielded Mash
|
120
|
+
json = metadata.to_iiif_json do |info|
|
121
|
+
info.tile_width = 512
|
122
|
+
info.tile_height = 512
|
123
|
+
info.formats = ['jpg', 'png']
|
124
|
+
info.qualities = ['native', 'grey']
|
125
|
+
info.profile = 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
|
126
|
+
info.image_host = 'http://myserver.com/image'
|
127
|
+
end
|
128
|
+
|
129
|
+
# If you want the xml flavor of a IIIF Info response, use
|
130
|
+
xml = metadata.to_iiif_xml
|
131
|
+
# It can be called with the same type of block as #to_iiif_json
|
132
|
+
|
93
133
|
== Commandline
|
94
134
|
|
95
135
|
There is also a little commandline utility for outputting the OpenURL.
|
@@ -102,14 +142,13 @@ There is also a little commandline utility for outputting the OpenURL.
|
|
102
142
|
|
103
143
|
$ djatoka_url --help
|
104
144
|
|
105
|
-
|
106
145
|
== TODO
|
107
146
|
- Testing the view helpers.
|
108
147
|
- View more examples of images where the dwtLevels differ from the Djatoka levels.
|
109
148
|
|
110
|
-
==
|
149
|
+
== Authors
|
111
150
|
|
112
|
-
Jason Ronallo
|
151
|
+
Jason Ronallo, Willy Mene
|
113
152
|
|
114
153
|
== Copyright
|
115
154
|
|
data/djatoka.gemspec
CHANGED
@@ -5,9 +5,9 @@
|
|
5
5
|
|
6
6
|
Gem::Specification.new do |s|
|
7
7
|
s.name = %q{djatoka}
|
8
|
-
s.version = "0.2.
|
9
|
-
s.authors = ["Jason Ronallo"]
|
10
|
-
s.email = %q{jronallo@gmail.com}
|
8
|
+
s.version = "0.2.3"
|
9
|
+
s.authors = ["Jason Ronallo", "Willy Mene"]
|
10
|
+
s.email = %q{jronallo@gmail.com wmene@stanford.edu}
|
11
11
|
s.homepage = %q{http://github.com/jronallo/djatoka}
|
12
12
|
s.summary = %q{Djatoka image server helpers for Ruby and Rails.}
|
13
13
|
s.description = %q{The djatoka library provides some simple methods for creation of the OpenURLs needed to communicate with the Djatoka image server.}
|
@@ -16,15 +16,17 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
17
17
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
18
18
|
s.require_paths = ["lib"]
|
19
|
-
|
19
|
+
|
20
20
|
s.add_dependency "addressable"
|
21
21
|
s.add_dependency "hashie"
|
22
22
|
s.add_dependency "json"
|
23
|
-
|
23
|
+
s.add_dependency "mime-types"
|
24
|
+
|
24
25
|
s.add_dependency "trollop"
|
25
26
|
|
26
27
|
s.add_development_dependency "mocha"
|
27
28
|
s.add_development_dependency "fakeweb"
|
28
29
|
s.add_development_dependency "shoulda"
|
30
|
+
s.add_development_dependency "equivalent-xml"
|
29
31
|
end
|
30
32
|
|
@@ -0,0 +1,163 @@
|
|
1
|
+
|
2
|
+
# For rotation param testing. Allows you to do the following with String:
|
3
|
+
# '123.456'.numeric? => true
|
4
|
+
class String
|
5
|
+
def numeric?
|
6
|
+
return true if self =~ /^\d+$/
|
7
|
+
true if Float(self) rescue false
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
module Djatoka
|
12
|
+
|
13
|
+
class IiifException < Exception; end
|
14
|
+
|
15
|
+
class IiifInvalidParam < IiifException
|
16
|
+
|
17
|
+
def initialize (param_name, value=nil)
|
18
|
+
@param_name = param_name
|
19
|
+
@value = value
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{@param_name.capitalize} is invalid: " << @value.to_s
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
# A class for translating IIIF parameters into a Djatoka::Region object.
|
29
|
+
#
|
30
|
+
# * See the {documentation for the IIIF API}[http://www-sul.stanford.edu/iiif/image-api/#size]
|
31
|
+
#
|
32
|
+
# It behaves like the Djatoka::Region object, in that you can chain methods together when setting params.
|
33
|
+
# Once you've set all the params, call #djatoka_region to translate the parameters. Validation of
|
34
|
+
# values occurs in this method, and a Djatoka::IiifInvalidParam exception is raised if any of the values
|
35
|
+
# are invalid.
|
36
|
+
#
|
37
|
+
# * Usage example
|
38
|
+
# resolver = Djatoka::Resolver.new('http://server.edu/adore-djatoka/resolver')
|
39
|
+
# id = 'someImageId1234'
|
40
|
+
#
|
41
|
+
# request = Djatoka::IiifRequest.new(resolver, id)
|
42
|
+
# djatoka_region = request.region('full').size('full').rotation('0').quality('native').format('jpg').djatoka_region
|
43
|
+
class IiifRequest
|
44
|
+
|
45
|
+
ALL_PARAMS = Set.new(['region', 'size', 'rotation', 'quality', 'format'])
|
46
|
+
|
47
|
+
attr_accessor :iiif_params
|
48
|
+
|
49
|
+
# You can set the params for the request in 2 ways:
|
50
|
+
# 1. Pass in params as a hash with the IIIF parameters {:id => 'some/id', :region => 'full'...}
|
51
|
+
# 2. Do not pass in params, and use the chain methods to set each value
|
52
|
+
def initialize(resolver, id, params = nil)
|
53
|
+
@id = id
|
54
|
+
@resolver = resolver
|
55
|
+
@iiif_params = Hashie::Mash.new
|
56
|
+
|
57
|
+
if(!params.nil? && params.is_a?(Hash))
|
58
|
+
params.keys.each do |k|
|
59
|
+
self.send("#{k}", params[k])
|
60
|
+
end
|
61
|
+
end
|
62
|
+
# else, params is nil, the caller will set each value
|
63
|
+
end
|
64
|
+
|
65
|
+
def id(v)
|
66
|
+
@iiif_params[:id] = v
|
67
|
+
self
|
68
|
+
end
|
69
|
+
|
70
|
+
def region(v)
|
71
|
+
@iiif_params[:region] = v
|
72
|
+
self
|
73
|
+
end
|
74
|
+
|
75
|
+
def size(v)
|
76
|
+
@iiif_params[:size] = v
|
77
|
+
self
|
78
|
+
end
|
79
|
+
|
80
|
+
def rotation(v)
|
81
|
+
@iiif_params[:rotation] = v
|
82
|
+
self
|
83
|
+
end
|
84
|
+
|
85
|
+
def quality(v)
|
86
|
+
@iiif_params[:quality] = v
|
87
|
+
self
|
88
|
+
end
|
89
|
+
|
90
|
+
def format(v)
|
91
|
+
@iiif_params[:format] = v
|
92
|
+
self
|
93
|
+
end
|
94
|
+
|
95
|
+
def all_params_present?
|
96
|
+
names = Set.new(@iiif_params.keys)
|
97
|
+
names == ALL_PARAMS
|
98
|
+
end
|
99
|
+
|
100
|
+
def djatoka_region
|
101
|
+
unless(all_params_present?)
|
102
|
+
current = Set.new(@iiif_params.keys)
|
103
|
+
missing = (ALL_PARAMS - current).to_a
|
104
|
+
msg = "Invalid IIIF request. The following params are missing: " << missing.join(',')
|
105
|
+
raise IiifException.new(msg)
|
106
|
+
end
|
107
|
+
|
108
|
+
region = @resolver.region(@iiif_params[:id])
|
109
|
+
|
110
|
+
if(@iiif_params[:region] =~ /^(\d+),(\d+),(\d+),(\d+)$/)
|
111
|
+
region.region("#{$2},#{$1},#{$4},#{$3}")
|
112
|
+
elsif(!(@iiif_params[:region] =~ /^full$/i))
|
113
|
+
raise IiifInvalidParam.new "region", @iiif_params[:region]
|
114
|
+
end
|
115
|
+
|
116
|
+
s = @iiif_params[:size]
|
117
|
+
case s
|
118
|
+
when /^full$/i
|
119
|
+
s #noop
|
120
|
+
when /^(\d+),$/
|
121
|
+
region.scale( ["#{$1}", "0"] ) #w => w,0
|
122
|
+
when /^,(\d+)$/
|
123
|
+
region.scale( ["0", "#{$1}"] ) #h => 0,h
|
124
|
+
when /^pct:(\d+)$/i
|
125
|
+
dj_scale = $1.to_f / 100.0
|
126
|
+
region.scale(dj_scale.to_s)
|
127
|
+
when /^(\d+),(\d+)$/
|
128
|
+
region.scale("#{$1},#{$2}")
|
129
|
+
# TODO Best Fit: when /^!(\d+),(\d+)$/
|
130
|
+
else
|
131
|
+
raise IiifInvalidParam.new "size", s
|
132
|
+
end
|
133
|
+
|
134
|
+
unless(@iiif_params[:rotation].numeric?)
|
135
|
+
raise IiifInvalidParam.new "rotation", @iiif_params[:rotation]
|
136
|
+
end
|
137
|
+
region.rotate(@iiif_params[:rotation])
|
138
|
+
|
139
|
+
f = @iiif_params[:format]
|
140
|
+
if(f)
|
141
|
+
if(f =~ /\//)
|
142
|
+
type = MIME::Types[f].first
|
143
|
+
else
|
144
|
+
type = MIME::Types.type_for(@iiif_params[:format]).first
|
145
|
+
end
|
146
|
+
raise IiifInvalidParam.new("format", f) if(type.nil?)
|
147
|
+
else
|
148
|
+
#default to jpg or let djatoka determine default
|
149
|
+
type = MIME::Types.type_for('jpg').first
|
150
|
+
end
|
151
|
+
region.format(type.to_s)
|
152
|
+
|
153
|
+
unless(@iiif_params[:quality] =~ /^(native|color|grey|bitonal)$/i)
|
154
|
+
raise IiifInvalidParam.new 'quality', @iiif_params[:quality]
|
155
|
+
end
|
156
|
+
|
157
|
+
region
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
data/lib/djatoka/metadata.rb
CHANGED
@@ -56,7 +56,7 @@ class Djatoka::Metadata
|
|
56
56
|
def all_levels
|
57
57
|
perform if !response # if we haven't already performed the metadata query do it now
|
58
58
|
levels_hash = Hashie::Mash.new
|
59
|
-
levels_i = levels.to_i
|
59
|
+
levels_i = levels.to_i
|
60
60
|
(0..levels_i).each do |level_num|
|
61
61
|
level_height = height.to_i
|
62
62
|
level_width = width.to_i
|
@@ -71,9 +71,94 @@ class Djatoka::Metadata
|
|
71
71
|
end
|
72
72
|
levels_hash
|
73
73
|
end
|
74
|
-
|
74
|
+
|
75
75
|
def max_level
|
76
|
-
all_levels.keys.sort.last
|
76
|
+
all_levels.keys.sort.last
|
77
|
+
end
|
78
|
+
|
79
|
+
# Builds a String containing the JSON response to a IIIF Image Information Request
|
80
|
+
#
|
81
|
+
# * {Documentation about the Image Info Request}[http://www-sul.stanford.edu/iiif/image-api/#info]
|
82
|
+
#
|
83
|
+
# It will fill in the required fields of identifier, width, and height. It will also fill in
|
84
|
+
# the scale_factors as determined from Djatoka::Metadata#levels
|
85
|
+
# The method yields a Mash where you can set the value of the optional fields. Here's an example:
|
86
|
+
#
|
87
|
+
# metadata.to_iiif_xml do |info|
|
88
|
+
# info.tile_width = 512
|
89
|
+
# info.tile_height = 512
|
90
|
+
# info.formats = ['jpg', 'png']
|
91
|
+
# info.qualities = ['native', 'grey']
|
92
|
+
# info.profile = 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
|
93
|
+
# info.image_host = 'http://myserver.com/image'
|
94
|
+
# end
|
95
|
+
def to_iiif_json(&block)
|
96
|
+
info = Hashie::Mash.new
|
97
|
+
info.identifier = @rft_id
|
98
|
+
info.width = @width.to_i
|
99
|
+
info.height = @height.to_i
|
100
|
+
info.scale_factors = levels_as_i
|
101
|
+
# optional fields map directly to json from the Mash
|
102
|
+
yield(info)
|
103
|
+
|
104
|
+
# convert strings to ints for tile width and height
|
105
|
+
info.tile_width = info.tile_width.to_i if(info.tile_width?)
|
106
|
+
info.tile_height = info.tile_height.to_i if(info.tile_height?)
|
107
|
+
JSON.pretty_generate(info)
|
108
|
+
end
|
109
|
+
|
110
|
+
# Builds a String containing the xml response to a IIIF Image Information Request
|
111
|
+
#
|
112
|
+
# * {Documentation about the Image Info Request}[http://www-sul.stanford.edu/iiif/image-api/#info]
|
113
|
+
#
|
114
|
+
# It will fill in the required fields of identifier, width, and height. It will also fill in
|
115
|
+
# the scale_factors as determined from Djatoka::Metadata#levels
|
116
|
+
# The method yields a Mash where you can set the values of the optional fields. Here's an example:
|
117
|
+
#
|
118
|
+
# metadata.to_iiif_json do |info|
|
119
|
+
# info.tile_width = 512
|
120
|
+
# info.tile_height = 512
|
121
|
+
# info.formats = ['jpg', 'png']
|
122
|
+
# info.qualities = ['native', 'grey']
|
123
|
+
# info.profile = 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
|
124
|
+
# info.image_host = 'http://myserver.com/image'
|
125
|
+
# end
|
126
|
+
def to_iiif_xml(&block)
|
127
|
+
builder = Nokogiri::XML::Builder.new do |xml|
|
128
|
+
info = Hashie::Mash.new
|
129
|
+
yield(info)
|
130
|
+
xml.info('xmlns' => 'http://library.stanford.edu/iiif/image-api/ns/') {
|
131
|
+
xml.identifier @rft_id
|
132
|
+
xml.width @width
|
133
|
+
xml.height @height
|
134
|
+
xml.scale_factors {
|
135
|
+
levels_as_i.each {|lvl| xml.scale_factor lvl}
|
136
|
+
}
|
137
|
+
|
138
|
+
#optional fields
|
139
|
+
xml.tile_width info.tile_width if(info.tile_width?)
|
140
|
+
xml.tile_height info.tile_height if(info.tile_height?)
|
141
|
+
if(info.formats?)
|
142
|
+
xml.formats {
|
143
|
+
info.formats.each {|mt| xml.format mt}
|
144
|
+
}
|
145
|
+
end
|
146
|
+
if(info.qualities?)
|
147
|
+
xml.qualities {
|
148
|
+
info.qualities.each {|q| xml.quality q}
|
149
|
+
}
|
150
|
+
end
|
151
|
+
xml.profile info.profile if(info.profile?)
|
152
|
+
xml.image_host info.image_host if(info.image_host?)
|
153
|
+
}
|
154
|
+
end
|
155
|
+
builder.to_xml
|
156
|
+
end
|
157
|
+
|
158
|
+
private
|
159
|
+
# Just the levels themselves, as a sorted array of integers
|
160
|
+
def levels_as_i
|
161
|
+
all_levels.keys.map{ |l| l.to_i}.sort
|
77
162
|
end
|
78
163
|
|
79
164
|
end
|
data/lib/djatoka/resolver.rb
CHANGED
@@ -63,6 +63,16 @@ class Djatoka::Resolver
|
|
63
63
|
Djatoka::Region.new(self, rft_id, params).uri
|
64
64
|
end
|
65
65
|
|
66
|
+
# Shortcut for creating a Djatoka::Region from a Hash of Iiif parameters.
|
67
|
+
def iiif_region(rft_id, params={})
|
68
|
+
Djatoka::IiifRequest.new(self, rft_id, params).djatoka_region
|
69
|
+
end
|
70
|
+
|
71
|
+
# Shortcut for creating an Addressable::URI from a Hash of Iiif parameters.
|
72
|
+
def iiif_uri(rft_id, params={})
|
73
|
+
Djatoka::IiifRequest.new(self, rft_id, params).djatoka_region.uri
|
74
|
+
end
|
75
|
+
|
66
76
|
def base_uri_params
|
67
77
|
params = {:host => host, :path => path, :scheme => scheme}
|
68
78
|
params[:port] = port if port
|
data/lib/djatoka.rb
CHANGED
@@ -52,17 +52,21 @@ end
|
|
52
52
|
require 'net/http'
|
53
53
|
require 'net/https'
|
54
54
|
require 'uri'
|
55
|
+
require 'set'
|
55
56
|
|
56
57
|
require 'djatoka/net'
|
57
58
|
require 'djatoka/resolver'
|
58
59
|
require 'djatoka/metadata'
|
59
60
|
require 'djatoka/common'
|
60
61
|
require 'djatoka/region'
|
62
|
+
require 'djatoka/iiif_request'
|
61
63
|
|
62
64
|
require 'addressable/uri'
|
63
65
|
require 'addressable/template'
|
64
66
|
require 'json'
|
67
|
+
require 'nokogiri'
|
65
68
|
require 'hashie'
|
69
|
+
require 'mime/types'
|
66
70
|
|
67
71
|
|
68
72
|
if defined? Rails
|
data/test/helper.rb
CHANGED
@@ -3,6 +3,7 @@ require 'test/unit'
|
|
3
3
|
require 'shoulda'
|
4
4
|
require 'fakeweb'
|
5
5
|
require 'pry'
|
6
|
+
require 'equivalent-xml'
|
6
7
|
|
7
8
|
$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
|
8
9
|
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
@@ -39,7 +40,7 @@ end
|
|
39
40
|
|
40
41
|
FakeWeb.register_uri(:get, "http://african.lanl.gov/adore-djatoka/resolver?rft_id=ua023_015-006-bx0003-014-075&svc_id=info%3Alanl-repo%2Fsvc%2FgetMetadata&url_ver=Z39.88-2004",
|
41
42
|
:response => File.read('test/fixtures/ua023_015-006-bx0003-014-075-metadata.json'))
|
42
|
-
|
43
|
+
|
43
44
|
FakeWeb.register_uri(:get, "http://african.lanl.gov/adore-djatoka/resolver?rft_id=asdf&svc_id=info%3Alanl-repo%2Fsvc%2FgetMetadata&url_ver=Z39.88-2004",
|
44
45
|
:response => File.read('test/fixtures/empty-metadata.json'))
|
45
46
|
FakeWeb.register_uri(:get, "http://african.lanl.gov/adore-djatoka/resolver?url_ver=Z39.88-2004&svc_id=info%3Alanl-repo%2Fsvc%2Fping&rft_id=asdf",
|
@@ -0,0 +1,173 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestDjatokaIiifRequest < Test::Unit::TestCase
|
4
|
+
with_a_resolver do
|
5
|
+
context 'creates Djatoka::Region objects' do
|
6
|
+
setup do
|
7
|
+
@req = Djatoka::IiifRequest.new(@resolver, @identifier)
|
8
|
+
end
|
9
|
+
|
10
|
+
context 'from a IIIF request with all defaults' do
|
11
|
+
setup do
|
12
|
+
@region = @req.region('full').size('full').rotation('0').quality('native').format('jpg').djatoka_region
|
13
|
+
end
|
14
|
+
|
15
|
+
should 'set region to nil from full' do
|
16
|
+
assert_nil @region.query.region
|
17
|
+
end
|
18
|
+
|
19
|
+
should 'set size to nil from full' do
|
20
|
+
assert_nil @region.query.scale
|
21
|
+
end
|
22
|
+
|
23
|
+
should 'set rotation to 0' do
|
24
|
+
assert_equal '0', @region.query.rotate
|
25
|
+
end
|
26
|
+
|
27
|
+
should 'set format to image/jpg' do
|
28
|
+
assert_equal 'image/jpeg', @region.query.format
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'translates region parameters' do
|
34
|
+
setup do
|
35
|
+
@req.size('full').rotation('0').quality('native').format('jpg')
|
36
|
+
end
|
37
|
+
|
38
|
+
should 'set x,y,w,h requests' do
|
39
|
+
reg = @req.region('10,20,50,100').djatoka_region
|
40
|
+
assert_equal '20,10,100,50', reg.query.region
|
41
|
+
end
|
42
|
+
|
43
|
+
should 'raise an exception if the region does not fit the x,y,w,h format, or is not "full"' do
|
44
|
+
assert_raise Djatoka::IiifInvalidParam do
|
45
|
+
@req.region('blah').djatoka_region
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
context 'translates size parameters' do
|
51
|
+
setup do
|
52
|
+
@req.region('10,20,50,100').rotation('0').quality('native').format('jpg')
|
53
|
+
end
|
54
|
+
|
55
|
+
should 'set "w," requests to the correct scale value' do
|
56
|
+
reg = @req.size('800,').djatoka_region
|
57
|
+
assert_equal '800,0', reg.query.scale
|
58
|
+
end
|
59
|
+
|
60
|
+
should 'set ",h" requests to the correct scale value' do
|
61
|
+
reg = @req.size(',900').djatoka_region
|
62
|
+
assert_equal '0,900', reg.query.scale
|
63
|
+
end
|
64
|
+
|
65
|
+
should 'set "pct:n" requests to the correct scale value' do
|
66
|
+
reg = @req.size('pct:75').djatoka_region
|
67
|
+
assert_equal '0.75', reg.query.scale
|
68
|
+
|
69
|
+
reg = @req.size('pct:125').djatoka_region
|
70
|
+
assert_equal '1.25', reg.query.scale
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'set "w,h" requests to the correct scale value' do
|
74
|
+
reg = @req.size('1024,768').djatoka_region
|
75
|
+
assert_equal '1024,768', reg.query.scale
|
76
|
+
end
|
77
|
+
|
78
|
+
should 'raise an exception if the value cannot be parsed into a Float' do
|
79
|
+
assert_raise Djatoka::IiifInvalidParam do
|
80
|
+
@req.size('pct:0.75').djatoka_region
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
context 'translates rotation parameters' do
|
87
|
+
setup do
|
88
|
+
@req.region('10,20,50,100').size('800,').quality('native').format('jpg')
|
89
|
+
end
|
90
|
+
|
91
|
+
should 'set values that are numeric' do
|
92
|
+
reg = @req.rotation('90').djatoka_region
|
93
|
+
assert_equal '90', reg.query.rotate
|
94
|
+
|
95
|
+
reg = @req.rotation('270').djatoka_region
|
96
|
+
assert_equal '270', reg.query.rotate
|
97
|
+
end
|
98
|
+
|
99
|
+
should 'raise an exception if the value is not numeric' do
|
100
|
+
assert_raise Djatoka::IiifInvalidParam do
|
101
|
+
@req.rotation('blah').djatoka_region
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
context 'translates format parameters' do
|
107
|
+
setup do
|
108
|
+
@req.region('full').size('full').rotation('0').quality('native')
|
109
|
+
end
|
110
|
+
|
111
|
+
should 'set the format from a valid extension as from the end of a URL' do
|
112
|
+
reg = @req.format('image.png').djatoka_region
|
113
|
+
assert_equal 'image/png', reg.query.format
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'set the format from a valid mime-type as from the "Accept:" HTTP header' do
|
117
|
+
reg = @req.format('image/tiff').djatoka_region
|
118
|
+
assert_equal 'image/tiff', reg.query.format
|
119
|
+
end
|
120
|
+
|
121
|
+
should 'raise an exception if the value is not a valid mime type extension' do
|
122
|
+
assert_raise Djatoka::IiifInvalidParam do
|
123
|
+
@req.format('nobody').djatoka_region
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
should 'raise an exception if the value is not a valid mime type value' do
|
128
|
+
assert_raise Djatoka::IiifInvalidParam do
|
129
|
+
@req.format('image/blahtype').djatoka_region
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'validates quaility parameters' do
|
135
|
+
setup do
|
136
|
+
@req.region('full').size('full').rotation('0').format('jpg')
|
137
|
+
end
|
138
|
+
|
139
|
+
should 'not raise when the quality is valid' do
|
140
|
+
assert_nothing_raised do
|
141
|
+
@req.quality('color').djatoka_region
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
should 'raise an exception when the quality is invalid' do
|
146
|
+
assert_raise Djatoka::IiifInvalidParam do
|
147
|
+
@req.quality('3d').djatoka_region
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
context '#all_params_present?' do
|
153
|
+
should 'return true when all the valid params have been set' do
|
154
|
+
@req.region('full').size('full').rotation('0').quality('native').format('jpg')
|
155
|
+
assert @req.all_params_present?
|
156
|
+
end
|
157
|
+
|
158
|
+
should 'return false when params are missing' do
|
159
|
+
@req.region('full').size('full').quality('native').format('jpg')
|
160
|
+
assert_equal false, @req.all_params_present?
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
context '#djatoka_region' do
|
165
|
+
should 'raise a IiifException if a required param is missing from the request' do
|
166
|
+
assert_raise Djatoka::IiifException do
|
167
|
+
@req.size('800,').djatoka_region
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end #context
|
172
|
+
end #with_a_resolver
|
173
|
+
end
|
data/test/test_metadata.rb
CHANGED
@@ -60,14 +60,14 @@ class TestDjatokaMetadata < Test::Unit::TestCase
|
|
60
60
|
end
|
61
61
|
end
|
62
62
|
|
63
|
-
context 'using net::https' do
|
64
|
-
should 'get metadata when using an https URI' do
|
63
|
+
context 'using net::https' do
|
64
|
+
should 'get metadata when using an https URI' do
|
65
65
|
Djatoka.use_curb=false
|
66
66
|
resolver = Djatoka::Resolver.new('https://scrc.lib.ncsu.edu/adore-djatoka/resolver')
|
67
67
|
metadata_obj = resolver.metadata('0004817')
|
68
68
|
metadata = metadata_obj.perform
|
69
69
|
assert_equal('10669', metadata.height)
|
70
|
-
end
|
70
|
+
end
|
71
71
|
end
|
72
72
|
|
73
73
|
context 'determining all the levels for a particular metadata response' do
|
@@ -92,7 +92,7 @@ class TestDjatokaMetadata < Test::Unit::TestCase
|
|
92
92
|
should 'know which is the max level' do
|
93
93
|
assert_equal "6", @metadata.max_level
|
94
94
|
end
|
95
|
-
|
95
|
+
|
96
96
|
should 'return appropriate height and width for all levels when levels and dwt_levels do not match' do
|
97
97
|
levels = {"0"=>{"height"=>58, "width"=>37},
|
98
98
|
"1"=>{"height"=>115, "width"=>74},
|
@@ -104,10 +104,85 @@ class TestDjatokaMetadata < Test::Unit::TestCase
|
|
104
104
|
assert_equal levels, returned_levels
|
105
105
|
|
106
106
|
end
|
107
|
-
|
107
|
+
|
108
108
|
end # levels
|
109
109
|
|
110
|
+
context 'IIIF Image Information Requests' do
|
111
|
+
setup do
|
112
|
+
@metadata_obj = @resolver.metadata(@identifier)
|
113
|
+
@metadata = @metadata_obj.perform
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'create json responses' do
|
117
|
+
iiif_json = <<-EOF
|
118
|
+
{
|
119
|
+
"identifier": "info:lanl-repo/ds/5aa182c2-c092-4596-af6e-e95d2e263de3",
|
120
|
+
"width": 5120,
|
121
|
+
"height": 3372,
|
122
|
+
"scale_factors": [ 0,1,2,3,4,5,6 ],
|
123
|
+
"tile_width": 512,
|
124
|
+
"tile_height": 512,
|
125
|
+
"formats": [ "jpg", "png" ],
|
126
|
+
"qualities": [ "native", "grey" ],
|
127
|
+
"profile": "http://library.stanford.edu/iiif/image-api/compliance.html#level1",
|
128
|
+
"image_host": "http://myserver.com/image"
|
129
|
+
}
|
130
|
+
EOF
|
131
|
+
expected = JSON.parse(iiif_json)
|
132
|
+
|
133
|
+
str = @metadata.to_iiif_json do |info|
|
134
|
+
info.tile_width = '512'
|
135
|
+
info.tile_height = 512 # tile_* can be string or int
|
136
|
+
info.formats = ['jpg', 'png']
|
137
|
+
info.qualities = ['native', 'grey']
|
138
|
+
info.profile = 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
|
139
|
+
info.image_host = 'http://myserver.com/image'
|
140
|
+
end
|
141
|
+
assert_equal expected, JSON.parse(str)
|
142
|
+
end
|
143
|
+
|
144
|
+
should 'create xml responses' do
|
145
|
+
iiif_xml =<<-EOXML
|
146
|
+
<info xmlns="http://library.stanford.edu/iiif/image-api/ns/">
|
147
|
+
<identifier>info:lanl-repo/ds/5aa182c2-c092-4596-af6e-e95d2e263de3</identifier>
|
148
|
+
<width>5120</width>
|
149
|
+
<height>3372</height>
|
150
|
+
<scale_factors>
|
151
|
+
<scale_factor>0</scale_factor>
|
152
|
+
<scale_factor>1</scale_factor>
|
153
|
+
<scale_factor>2</scale_factor>
|
154
|
+
<scale_factor>3</scale_factor>
|
155
|
+
<scale_factor>4</scale_factor>
|
156
|
+
<scale_factor>5</scale_factor>
|
157
|
+
<scale_factor>6</scale_factor>
|
158
|
+
</scale_factors>
|
159
|
+
<tile_width>512</tile_width>
|
160
|
+
<tile_height>512</tile_height>
|
161
|
+
<formats>
|
162
|
+
<format>jpg</format>
|
163
|
+
<format>png</format>
|
164
|
+
</formats>
|
165
|
+
<qualities>
|
166
|
+
<quality>native</quality>
|
167
|
+
<quality>grey</quality>
|
168
|
+
</qualities>
|
169
|
+
<profile>http://library.stanford.edu/iiif/image-api/compliance.html#level1</profile>
|
170
|
+
<image_host>http://myserver.com/image</image_host>
|
171
|
+
</info>
|
172
|
+
EOXML
|
173
|
+
|
174
|
+
str = @metadata.to_iiif_xml do |info|
|
175
|
+
info.tile_width = '512'
|
176
|
+
info.tile_height = 512 # tile_* can be string or int
|
177
|
+
info.formats = ['jpg', 'png']
|
178
|
+
info.qualities = ['native', 'grey']
|
179
|
+
info.profile = 'http://library.stanford.edu/iiif/image-api/compliance.html#level1'
|
180
|
+
info.image_host = 'http://myserver.com/image'
|
181
|
+
end
|
182
|
+
assert EquivalentXml.equivalent?(str, iiif_xml)
|
183
|
+
end
|
184
|
+
end #IIIF Info Responses
|
185
|
+
|
110
186
|
end #with_a_resolver
|
111
187
|
|
112
188
|
end
|
113
|
-
|
data/test/test_resolver.rb
CHANGED
@@ -58,6 +58,26 @@ class TestDjatokaResolver < Test::Unit::TestCase
|
|
58
58
|
assert @resolver.region_uri(@identifier).is_a? Addressable::URI
|
59
59
|
end
|
60
60
|
|
61
|
+
context 'Iiif Requests' do
|
62
|
+
setup do
|
63
|
+
@iiif = {
|
64
|
+
:region => 'full',
|
65
|
+
:size => 'full',
|
66
|
+
:rotation => '0',
|
67
|
+
:quality => 'native',
|
68
|
+
:format => 'jpg'
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
should 'create a region from a hash of IIIF parameters' do
|
73
|
+
assert @resolver.iiif_region(@identifier, @iiif).is_a? Djatoka::Region
|
74
|
+
end
|
75
|
+
|
76
|
+
should 'create a region uri from a hash of IIIF parameters' do
|
77
|
+
assert @resolver.iiif_uri(@identifier, @iiif).is_a? Addressable::URI
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
61
81
|
end #context a Djatoka::Resolver
|
62
82
|
|
63
83
|
context 'a resolver with a port number' do
|
metadata
CHANGED
@@ -1,131 +1,145 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: djatoka
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
5
|
-
prerelease:
|
4
|
+
version: 0.2.3
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Jason Ronallo
|
8
|
+
- Willy Mene
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-04-
|
12
|
+
date: 2013-04-28 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: addressable
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
|
-
none: false
|
18
17
|
requirements:
|
19
|
-
- -
|
18
|
+
- - '>='
|
20
19
|
- !ruby/object:Gem::Version
|
21
20
|
version: '0'
|
22
21
|
type: :runtime
|
23
22
|
prerelease: false
|
24
23
|
version_requirements: !ruby/object:Gem::Requirement
|
25
|
-
none: false
|
26
24
|
requirements:
|
27
|
-
- -
|
25
|
+
- - '>='
|
28
26
|
- !ruby/object:Gem::Version
|
29
27
|
version: '0'
|
30
28
|
- !ruby/object:Gem::Dependency
|
31
29
|
name: hashie
|
32
30
|
requirement: !ruby/object:Gem::Requirement
|
33
|
-
none: false
|
34
31
|
requirements:
|
35
|
-
- -
|
32
|
+
- - '>='
|
36
33
|
- !ruby/object:Gem::Version
|
37
34
|
version: '0'
|
38
35
|
type: :runtime
|
39
36
|
prerelease: false
|
40
37
|
version_requirements: !ruby/object:Gem::Requirement
|
41
|
-
none: false
|
42
38
|
requirements:
|
43
|
-
- -
|
39
|
+
- - '>='
|
44
40
|
- !ruby/object:Gem::Version
|
45
41
|
version: '0'
|
46
42
|
- !ruby/object:Gem::Dependency
|
47
43
|
name: json
|
48
44
|
requirement: !ruby/object:Gem::Requirement
|
49
|
-
none: false
|
50
45
|
requirements:
|
51
|
-
- -
|
46
|
+
- - '>='
|
52
47
|
- !ruby/object:Gem::Version
|
53
48
|
version: '0'
|
54
49
|
type: :runtime
|
55
50
|
prerelease: false
|
56
51
|
version_requirements: !ruby/object:Gem::Requirement
|
57
|
-
none: false
|
58
52
|
requirements:
|
59
|
-
- -
|
53
|
+
- - '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
- !ruby/object:Gem::Dependency
|
57
|
+
name: mime-types
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
63
|
+
type: :runtime
|
64
|
+
prerelease: false
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
60
68
|
- !ruby/object:Gem::Version
|
61
69
|
version: '0'
|
62
70
|
- !ruby/object:Gem::Dependency
|
63
71
|
name: trollop
|
64
72
|
requirement: !ruby/object:Gem::Requirement
|
65
|
-
none: false
|
66
73
|
requirements:
|
67
|
-
- -
|
74
|
+
- - '>='
|
68
75
|
- !ruby/object:Gem::Version
|
69
76
|
version: '0'
|
70
77
|
type: :runtime
|
71
78
|
prerelease: false
|
72
79
|
version_requirements: !ruby/object:Gem::Requirement
|
73
|
-
none: false
|
74
80
|
requirements:
|
75
|
-
- -
|
81
|
+
- - '>='
|
76
82
|
- !ruby/object:Gem::Version
|
77
83
|
version: '0'
|
78
84
|
- !ruby/object:Gem::Dependency
|
79
85
|
name: mocha
|
80
86
|
requirement: !ruby/object:Gem::Requirement
|
81
|
-
none: false
|
82
87
|
requirements:
|
83
|
-
- -
|
88
|
+
- - '>='
|
84
89
|
- !ruby/object:Gem::Version
|
85
90
|
version: '0'
|
86
91
|
type: :development
|
87
92
|
prerelease: false
|
88
93
|
version_requirements: !ruby/object:Gem::Requirement
|
89
|
-
none: false
|
90
94
|
requirements:
|
91
|
-
- -
|
95
|
+
- - '>='
|
92
96
|
- !ruby/object:Gem::Version
|
93
97
|
version: '0'
|
94
98
|
- !ruby/object:Gem::Dependency
|
95
99
|
name: fakeweb
|
96
100
|
requirement: !ruby/object:Gem::Requirement
|
97
|
-
none: false
|
98
101
|
requirements:
|
99
|
-
- -
|
102
|
+
- - '>='
|
100
103
|
- !ruby/object:Gem::Version
|
101
104
|
version: '0'
|
102
105
|
type: :development
|
103
106
|
prerelease: false
|
104
107
|
version_requirements: !ruby/object:Gem::Requirement
|
105
|
-
none: false
|
106
108
|
requirements:
|
107
|
-
- -
|
109
|
+
- - '>='
|
108
110
|
- !ruby/object:Gem::Version
|
109
111
|
version: '0'
|
110
112
|
- !ruby/object:Gem::Dependency
|
111
113
|
name: shoulda
|
112
114
|
requirement: !ruby/object:Gem::Requirement
|
113
|
-
none: false
|
114
115
|
requirements:
|
115
|
-
- -
|
116
|
+
- - '>='
|
117
|
+
- !ruby/object:Gem::Version
|
118
|
+
version: '0'
|
119
|
+
type: :development
|
120
|
+
prerelease: false
|
121
|
+
version_requirements: !ruby/object:Gem::Requirement
|
122
|
+
requirements:
|
123
|
+
- - '>='
|
124
|
+
- !ruby/object:Gem::Version
|
125
|
+
version: '0'
|
126
|
+
- !ruby/object:Gem::Dependency
|
127
|
+
name: equivalent-xml
|
128
|
+
requirement: !ruby/object:Gem::Requirement
|
129
|
+
requirements:
|
130
|
+
- - '>='
|
116
131
|
- !ruby/object:Gem::Version
|
117
132
|
version: '0'
|
118
133
|
type: :development
|
119
134
|
prerelease: false
|
120
135
|
version_requirements: !ruby/object:Gem::Requirement
|
121
|
-
none: false
|
122
136
|
requirements:
|
123
|
-
- -
|
137
|
+
- - '>='
|
124
138
|
- !ruby/object:Gem::Version
|
125
139
|
version: '0'
|
126
140
|
description: The djatoka library provides some simple methods for creation of the
|
127
141
|
OpenURLs needed to communicate with the Djatoka image server.
|
128
|
-
email: jronallo@gmail.com
|
142
|
+
email: jronallo@gmail.com wmene@stanford.edu
|
129
143
|
executables:
|
130
144
|
- djatoka_url
|
131
145
|
extensions: []
|
@@ -158,6 +172,7 @@ files:
|
|
158
172
|
- init.rb
|
159
173
|
- lib/djatoka.rb
|
160
174
|
- lib/djatoka/common.rb
|
175
|
+
- lib/djatoka/iiif_request.rb
|
161
176
|
- lib/djatoka/metadata.rb
|
162
177
|
- lib/djatoka/net.rb
|
163
178
|
- lib/djatoka/region.rb
|
@@ -173,6 +188,7 @@ files:
|
|
173
188
|
- test/helper.rb
|
174
189
|
- test/test_common.rb
|
175
190
|
- test/test_djatoka.rb
|
191
|
+
- test/test_iiif_request.rb
|
176
192
|
- test/test_metadata.rb
|
177
193
|
- test/test_region.rb
|
178
194
|
- test/test_resolver.rb
|
@@ -180,27 +196,25 @@ files:
|
|
180
196
|
- watchr.rb
|
181
197
|
homepage: http://github.com/jronallo/djatoka
|
182
198
|
licenses: []
|
199
|
+
metadata: {}
|
183
200
|
post_install_message:
|
184
201
|
rdoc_options: []
|
185
202
|
require_paths:
|
186
203
|
- lib
|
187
204
|
required_ruby_version: !ruby/object:Gem::Requirement
|
188
|
-
none: false
|
189
205
|
requirements:
|
190
|
-
- -
|
206
|
+
- - '>='
|
191
207
|
- !ruby/object:Gem::Version
|
192
208
|
version: '0'
|
193
209
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
194
|
-
none: false
|
195
210
|
requirements:
|
196
|
-
- -
|
211
|
+
- - '>='
|
197
212
|
- !ruby/object:Gem::Version
|
198
213
|
version: '0'
|
199
214
|
requirements: []
|
200
215
|
rubyforge_project: djatoka
|
201
|
-
rubygems_version:
|
216
|
+
rubygems_version: 2.0.0.rc.2
|
202
217
|
signing_key:
|
203
|
-
specification_version:
|
218
|
+
specification_version: 4
|
204
219
|
summary: Djatoka image server helpers for Ruby and Rails.
|
205
220
|
test_files: []
|
206
|
-
has_rdoc:
|