1 (function(){ // hide everything externally to avoid name collisions
3 // whether to display debugging output
6 // whether we are locally debugging (i.e. the page is downloaded to our
7 // hard drive and served from a local server to ease development)
10 // the full URL to where svg.js is located
11 // Note: Update this before putting on production
14 svgSrcURL
= wgScriptPath
+ '/extensions/SVGZoom/src/svg.js';
16 svgSrcURL
= wgScriptPath
+ '/extensions/SVGZoom/src/svg.js';
19 // whether the pan and zoom UI is initialized
20 var svgUIReady
= false;
22 // a reference to the SVG OBJECT on the page
25 // a reference to our zoom and pan controls
28 // a reference to our SVG root tag
31 var isWebkit
= (Math
.max(navigator
.appVersion
.indexOf('WebKit'),
32 navigator
.appVersion
.indexOf('Safari'), 0));
35 if (navigator
.userAgent
.indexOf('Gecko') >= 0) {
36 isFF
= parseFloat(navigator
.userAgent
.split('Firefox/')[1]) || undefined;
39 // the URL to the proxy from which we can fetch SVG images within the same
40 // domain as this page is served from
41 // TODO: define once a proxy or API call is setup
43 // the location of our images
46 // for local debugging
47 var imageRoot
= '/images/';
49 'searchtool': imageRoot
+ 'searchtool.png',
50 'controls-north-mini': imageRoot
+ 'north-mini.png',
51 'controls-west-mini': imageRoot
+ 'west-mini.png',
52 'controls-east-mini': imageRoot
+ 'east-mini.png',
53 'controls-south-mini': imageRoot
+ 'south-mini.png',
54 'controls-zoom-plus-mini': imageRoot
+ 'zoom-plus-mini.png',
55 'controls-zoom-world-mini': imageRoot
+ 'zoom-world-mini.png',
56 'controls-zoom-minus-mini': imageRoot
+ 'zoom-minus-mini.png'
59 // Note: update this before putting on production
60 var imageRoot
= wgScriptPath
+ '/extensions/SVGZoom/images/';
62 'searchtool': imageRoot
+ 'searchtool.png',
63 'controls-north-mini': imageRoot
+ 'north-mini.png',
64 'controls-west-mini': imageRoot
+ 'west-mini.png',
65 'controls-east-mini': imageRoot
+ 'east-mini.png',
66 'controls-south-mini': imageRoot
+ 'south-mini.png',
67 'controls-zoom-plus-mini': imageRoot
+ 'zoom-plus-mini.png',
68 'controls-zoom-world-mini': imageRoot
+ 'zoom-world-mini.png',
69 'controls-zoom-minus-mini': imageRoot
+ 'zoom-minus-mini.png'
73 // determines if we are at a Wikimedia Commons detail page for an SVG file
74 function isSVGPage() {
75 if (wgNamespaceNumber
== 6 && wgTitle
&& wgTitle
.indexOf('.svg') != -1
76 && wgAction
== 'view') {
83 // Determines whether this page has image annotation enabled (i.e. the
84 // Add Note button). If this is enabled the DOM changes slightly and we have
85 // to account for it. Some SVG images have this (Tux.svg); others don't
86 // (Commons-logo.svg, for example).
87 function hasAnnotation() {
88 if (document
.getElementById('ImageAnnotationAddButton')) {
95 // inserts the SVG Web library into the page
96 function insertSVGWeb() {
97 document
.write('<script type="text/javascript" '
98 + 'src="' + svgSrcURL
+ '" '
99 + 'data-path="../../../../src" ' /* Note: remove before production */
100 + 'data-debug="' + svgDebug
+ '"></script>');
103 // adds a button that when pressed turns on the zoom and pan UI
104 function addStartButton() {
105 // are we already present? user could have hit back button on an old
107 if (document
.getElementById('SVGZoom.startButton')) {
111 // insert ourselves beside the SVG thumbnail area
112 var info
= getSVGInfo();
113 var thumbnail
= info
.fileNode
;
114 if (hasAnnotation()) {
115 thumbnail
= thumbnail
.childNodes
[0].childNodes
[0];
117 // make the container element we will go into a bit larger to accommodate
119 var infoWidth
= Number(String(info
.width
).replace('px', ''));
120 thumbnail
.style
.width
= (infoWidth
+ 30) + 'px';
121 var img
= document
.createElement('img');
122 img
.id
= 'SVGZoom.startButton';
123 img
.src
= imageBundle
['searchtool'];
124 img
.setAttribute('width', '30px');
125 img
.setAttribute('height', '30px');
126 img
.style
.position
= 'absolute';
127 img
.style
.cursor
= 'pointer';
128 img
.onclick
= initUI
;
129 // some SVG pages have a spurious <br/> element; add before that
130 if (thumbnail
.lastChild
.nodeType
== 1
131 && thumbnail
.lastChild
.nodeName
.toLowerCase() == 'br') {
132 thumbnail
.insertBefore(img
, thumbnail
.lastChild
);
134 thumbnail
.appendChild(img
);
138 // adds the pan and zoom UI and turns the PNG into an SVG object
140 if (svgUIReady
) { // already initialized
146 // remove magnifying glass icon
147 var startButton
= document
.getElementById('SVGZoom.startButton');
148 startButton
.parentNode
.removeChild(startButton
);
150 // get the thumbnail container and make it invisible
151 var info
= getSVGInfo();
152 var thumbnail
= info
.fileNode
;
153 if (hasAnnotation()) {
154 thumbnail
= thumbnail
.childNodes
[0].childNodes
[0];
156 var oldPNG
= thumbnail
.childNodes
[0];
157 oldPNG
.style
.visibility
= 'hidden';
158 oldPNG
.style
.zIndex
= -1000;
160 // store a reference to the SVG root to make subsequent accesses faster
161 svgRoot
= svgObject
.contentDocument
.rootElement
;
163 // Safari/Native has a bug where it doesn't respect the height/width of
164 // the OBJECT when scaling the size of some objects (commons-logo.svg,
165 // for example). A workaround is to manually set the size inside the SVG.
167 svgRoot
.setAttribute('width', info
.width
);
168 svgRoot
.setAttribute('height', info
.height
);
171 // reveal the SVG object and controls
172 svgObject
.parentNode
.style
.zIndex
= 1000;
173 svgControls
.style
.display
= 'block';
175 // make the cursor a hand when over the SVG; not all browsers support
177 svgRoot
.setAttribute('cursor', 'pointer');
178 // TODO: Get hand cursor showing up in SVG Web's Flash renderer
180 // add drag listeners on the SVG root
181 svgRoot
.addEventListener('mousedown', mouseDown
, false);
182 svgRoot
.addEventListener('mousemove', mouseMove
, false);
183 svgRoot
.addEventListener('mouseup', mouseUp
, false);
186 // Creates the SVG OBJECT during page load so that when we swap the PNG
187 // thumbnail and the SVG OBJECT it happens much faster
188 function createSVGObject() {
189 var info
= getSVGInfo();
190 var thumbnail
= info
.fileNode
;
191 if (hasAnnotation()) {
192 thumbnail
= thumbnail
.childNodes
[0].childNodes
[0];
195 // create the SVG OBJECT that will replace our thumbnail container
196 var obj
= document
.createElement('object', true);
197 obj
.setAttribute('type', 'image/svg+xml');
198 obj
.setAttribute('data', info
.url
);
199 obj
.setAttribute('width', info
.width
);
200 obj
.setAttribute('height', info
.height
);
201 obj
.addEventListener('load', function() {
202 // store a reference to the SVG OBJECT
205 // create the controls
206 svgControls
= createControls();
207 svgControls
.style
.display
= 'none';
209 // now place the controls on top of the SVG object
210 if (thumbnail
.lastChild
.nodeType
== 1
211 && thumbnail
.lastChild
.nodeName
.toLowerCase() == 'br') {
212 thumbnail
.insertBefore(svgControls
, thumbnail
.lastChild
);
214 thumbnail
.appendChild(svgControls
);
217 // add our magnification icon
220 // set up the mouse scroll wheel; FF 3.5/Native has an annoying bug
221 // where DOMMouseScroll events do not propagate to OBJECTs under
223 if (isFF
&& svgweb
.getHandlerType() == 'native') {
224 hookEvent(svgObject
.contentDocument
.rootElement
, 'mousewheel',
227 hookEvent('file', 'mousewheel', MouseWheel
);
230 // prevent IE memory leaks
231 thumbnail
= obj
= null;
233 // ensure that the thumbnail container has relative positioning; this will
234 // reset our absolutely positioned elements to be relative to our parent
235 // so we have correct coordinates
236 thumbnail
.style
.position
= 'relative';
237 // position object behind the PNG image; do it in a DIV to avoid any
238 // strange style + OBJECT interactions
239 var container
= document
.createElement('div');
240 container
.id
= 'SVGZoom.container';
241 container
.style
.zIndex
= -1000;
242 container
.style
.position
= 'absolute';
243 // FIXME: This is a hack; figure out why the Flash version of Commons-logo.svg
244 // is off by one 1 pixel on x and y
245 if (!hasAnnotation() && svgweb
.getHandlerType() == 'flash') {
246 container
.style
.top
= '-1px';
247 container
.style
.left
= '-1px';
249 container
.style
.top
= '0px';
250 container
.style
.left
= '0px';
252 if (thumbnail
.lastChild
.nodeType
== 1
253 && thumbnail
.lastChild
.nodeName
.toLowerCase() == 'br') {
254 thumbnail
.insertBefore(container
, thumbnail
.lastChild
);
256 thumbnail
.appendChild(container
);
258 svgweb
.appendChild(obj
, container
);
261 // Returns a DIV ready to append to the page with our zoom and pan controls
262 function createControls() {
263 var controls
= document
.createElement('div');
264 controls
.id
= 'SVGZoom.controls';
266 '<div style="position: absolute; left: 4px; top: 4px; z-index: 1004;" unselectable="on">'
267 + '<div id="SVGZoom.panup" style="position: absolute; left: 13px; top: 4px; width: 18px; height: 18px;">'
268 + ' <img id="SVGZoom.panup.innerImage" style="position: relative; width: 18px; height: 18px;" '
269 + 'src="' + imageBundle
['controls-north-mini'] + '"/>'
271 + '<div id="SVGZoom.panleft" style="position: absolute; left: 4px; top: 22px; width: 18px; height: 18px;">'
272 + ' <img id="SVGZoom.panleft.innerImage" style="position: relative; width: 18px; height: 18px;" '
273 + 'src="' + imageBundle
['controls-west-mini'] + '"/>'
275 + '<div id="SVGZoom.panright" style="position: absolute; left: 22px; top: 22px; width: 18px; height: 18px;">'
276 + ' <img id="SVGZoom.panright.innerImage" style="position: relative; width: 18px; height: 18px;" '
277 + 'src="' + imageBundle
['controls-east-mini'] + '"/>'
279 + '<div id="SVGZoom.pandown" style="position: absolute; left: 13px; top: 40px; width: 18px; height: 18px;">'
280 + ' <img id="SVGZoom.pandown.innerImage" style="position: relative; width: 18px; height: 18px;" '
281 + 'src="' + imageBundle
['controls-south-mini'] + '"/>'
283 + '<div id="SVGZoom.zoomin" style="position: absolute; left: 13px; top: 63px; width: 18px; height: 18px;">'
284 + ' <img id="SVGZoom.zoomin.innerImage" style="position: relative; width: 18px; height: 18px;" '
285 + 'src="' + imageBundle
['controls-zoom-plus-mini'] + '"/>'
287 + '<div id="SVGZoom.zoomworld" style="position: absolute; left: 13px; top: 81px; width: 18px; height: 18px;">'
288 + ' <img id="SVGZoom.zoomworld.innerImage" style="position: relative; width: 18px; height: 18px;" '
289 + 'src="' + imageBundle
['controls-zoom-world-mini'] + '"/>'
291 + '<div id="SVGZoom.zoomout" style="position: absolute; left: 13px; top: 99px; width: 18px; height: 18px;">'
292 + ' <img id="SVGZoom.zoomout.innerImage" style="position: relative; width: 18px; height: 18px;" '
293 + 'src="' + imageBundle
['controls-zoom-minus-mini'] + '"/>'
297 // attach event handlers
298 controls
.childNodes
[0].childNodes
[0].onclick
= panUp
;
299 controls
.childNodes
[0].childNodes
[1].onclick
= panLeft
;
300 controls
.childNodes
[0].childNodes
[2].onclick
= panRight
;
301 controls
.childNodes
[0].childNodes
[3].onclick
= panDown
;
302 controls
.childNodes
[0].childNodes
[4].onclick
= zoomIn
;
303 controls
.childNodes
[0].childNodes
[5].onclick
= zoomWorld
;
304 controls
.childNodes
[0].childNodes
[6].onclick
= zoomOut
;
310 svgRoot
.currentTranslate
.setY(svgRoot
.currentTranslate
.getY() - 25);
314 svgRoot
.currentTranslate
.setX(svgRoot
.currentTranslate
.getX() - 25);
317 function panRight() {
318 svgRoot
.currentTranslate
.setX(svgRoot
.currentTranslate
.getX() + 25);
322 svgRoot
.currentTranslate
.setY(svgRoot
.currentTranslate
.getY() + 25);
326 svgRoot
.currentScale
= svgRoot
.currentScale
* 1.1;
329 function zoomWorld() {
330 svgRoot
.currentScale
= 1;
331 svgRoot
.currentTranslate
.setXY(0, 0);
335 svgRoot
.currentScale
= svgRoot
.currentScale
/ 1.1;
338 // variables used for dragging
339 var x
, y
, rootX
, rootY
;
340 var dragX
= 0, dragY
= 0;
341 var dragging
= false;
343 function mouseDown(evt
) {
349 rootX
= svgRoot
.currentTranslate
.getX();
350 rootY
= svgRoot
.currentTranslate
.getY();
352 evt
.preventDefault(true);
355 function mouseMove(evt
) {
360 dragX
= evt
.clientX
- x
;
361 dragY
= evt
.clientY
- y
;
363 // Firefox and Webkit differ on the coordinates they return; Firefox
364 // returns the mouse coordinates with no scaling, while Webkit and SVG Web
365 // scale the mouse coordinates using the currentScale
366 if (isWebkit
|| svgweb
.getHandlerType() == 'flash') {
367 dragX
/= svgRoot
.currentScale
;
368 dragY
/= svgRoot
.currentScale
;
371 var newX
= rootX
+ dragX
;
372 var newY
= rootY
+ dragY
;
374 svgRoot
.currentTranslate
.setXY(newX
, newY
);
376 evt
.preventDefault(true);
379 function mouseUp(evt
) {
382 evt
.preventDefault(true);
385 // Returns a data structure that has info about the SVG file on this page, including:
386 // url - filename and URL necessary to fetch the SVG file
387 // width and height - the width and height to make the SVG file
388 // fileNode - the DOM node that has the top level PNG thumbnail in it to replace
389 // imgNode - the actual IMG tag that has the PNG thumbnail inside of it
390 // Note that this method returns null if there is no file node on the page.
391 function getSVGInfo() {
392 var fileNode
= document
.getElementById('file');
398 if (hasAnnotation()) {
399 url
= fileNode
.childNodes
[0].childNodes
[0].childNodes
[0].href
;
401 url
= fileNode
.childNodes
[0].href
;
403 var imgNode
= fileNode
.getElementsByTagName('img')[0];
404 var width
= imgNode
.getAttribute('width');
405 var height
= imgNode
.getAttribute('height');
407 return { url
: url
, fileNode
: fileNode
, imgNode
: imgNode
,
408 width
: width
, height
: height
};
411 // Mousewheel Scrolling thanks to
412 // http://blog.paranoidferret.com/index.php/2007/10/31/javascript-tutorial-the-scroll-wheel/
413 function hookEvent(element
, eventName
, callback
) {
414 if (typeof(element
) == 'string') {
415 element
= document
.getElementById(element
);
418 if (element
== null) {
422 if (element
.addEventListener
) {
423 if (eventName
== 'mousewheel') {
424 element
.addEventListener('DOMMouseScroll', callback
, false);
426 element
.addEventListener(eventName
, callback
, false);
427 } else if (element
.attachEvent
) {
428 element
.attachEvent("on" + eventName
, callback
);
432 function MouseWheel(e
) {
433 e
= e
? e
: window
.event
;
435 var wheelData
= e
.detail
? e
.detail
* -1 : e
.wheelDelta
;
443 if (e
.preventDefault
) {
450 // called when the page is loaded and ready to be manipulated
451 function pageLoaded() {
452 svgweb
.addOnLoad(createSVGObject
);
457 addOnloadHook(pageLoaded
);
460 // hide internal implementation details inside of a closure