diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddf9a42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +# Node.js +node_modules + +# pnpm +.pnpm + +# TypeScript +dist +*.tsbuildinfo diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..0a04128 --- /dev/null +++ b/LICENSE @@ -0,0 +1,165 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + + This version of the GNU Lesser General Public License incorporates +the terms and conditions of version 3 of the GNU General Public +License, supplemented by the additional permissions listed below. + + 0. Additional Definitions. + + As used herein, "this License" refers to version 3 of the GNU Lesser +General Public License, and the "GNU GPL" refers to version 3 of the GNU +General Public License. + + "The Library" refers to a covered work governed by this License, +other than an Application or a Combined Work as defined below. + + An "Application" is any work that makes use of an interface provided +by the Library, but which is not otherwise based on the Library. +Defining a subclass of a class defined by the Library is deemed a mode +of using an interface provided by the Library. + + A "Combined Work" is a work produced by combining or linking an +Application with the Library. The particular version of the Library +with which the Combined Work was made is also called the "Linked +Version". + + The "Minimal Corresponding Source" for a Combined Work means the +Corresponding Source for the Combined Work, excluding any source code +for portions of the Combined Work that, considered in isolation, are +based on the Application, and not on the Linked Version. + + The "Corresponding Application Code" for a Combined Work means the +object code and/or source code for the Application, including any data +and utility programs needed for reproducing the Combined Work from the +Application, but excluding the System Libraries of the Combined Work. + + 1. Exception to Section 3 of the GNU GPL. + + You may convey a covered work under sections 3 and 4 of this License +without being bound by section 3 of the GNU GPL. + + 2. Conveying Modified Versions. + + If you modify a copy of the Library, and, in your modifications, a +facility refers to a function or data to be supplied by an Application +that uses the facility (other than as an argument passed when the +facility is invoked), then you may convey a copy of the modified +version: + + a) under this License, provided that you make a good faith effort to + ensure that, in the event an Application does not supply the + function or data, the facility still operates, and performs + whatever part of its purpose remains meaningful, or + + b) under the GNU GPL, with none of the additional permissions of + this License applicable to that copy. + + 3. Object Code Incorporating Material from Library Header Files. + + The object code form of an Application may incorporate material from +a header file that is part of the Library. You may convey such object +code under terms of your choice, provided that, if the incorporated +material is not limited to numerical parameters, data structure +layouts and accessors, or small macros, inline functions and templates +(ten or fewer lines in length), you do both of the following: + + a) Give prominent notice with each copy of the object code that the + Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the object code with a copy of the GNU GPL and this license + document. + + 4. Combined Works. + + You may convey a Combined Work under terms of your choice that, +taken together, effectively do not restrict modification of the +portions of the Library contained in the Combined Work and reverse +engineering for debugging such modifications, if you also do each of +the following: + + a) Give prominent notice with each copy of the Combined Work that + the Library is used in it and that the Library and its use are + covered by this License. + + b) Accompany the Combined Work with a copy of the GNU GPL and this license + document. + + c) For a Combined Work that displays copyright notices during + execution, include the copyright notice for the Library among + these notices, as well as a reference directing the user to the + copies of the GNU GPL and this license document. + + d) Do one of the following: + + 0) Convey the Minimal Corresponding Source under the terms of this + License, and the Corresponding Application Code in a form + suitable for, and under terms that permit, the user to + recombine or relink the Application with a modified version of + the Linked Version to produce a modified Combined Work, in the + manner specified by section 6 of the GNU GPL for conveying + Corresponding Source. + + 1) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (a) uses at run time + a copy of the Library already present on the user's computer + system, and (b) will operate properly with a modified version + of the Library that is interface-compatible with the Linked + Version. + + e) Provide Installation Information, but only if you would otherwise + be required to provide such information under section 6 of the + GNU GPL, and only to the extent that such information is + necessary to install and execute a modified version of the + Combined Work produced by recombining or relinking the + Application with a modified version of the Linked Version. (If + you use option 4d0, the Installation Information must accompany + the Minimal Corresponding Source and Corresponding Application + Code. If you use option 4d1, you must provide the Installation + Information in the manner specified by section 6 of the GNU GPL + for conveying Corresponding Source.) + + 5. Combined Libraries. + + You may place library facilities that are a work based on the +Library side by side in a single library together with other library +facilities that are not Applications and are not covered by this +License, and convey such a combined library under terms of your +choice, if you do both of the following: + + a) Accompany the combined library with a copy of the same work based + on the Library, uncombined with any other library facilities, + conveyed under the terms of this License. + + b) Give prominent notice with the combined library that part of it + is a work based on the Library, and explaining where to find the + accompanying uncombined form of the same work. + + 6. Revised Versions of the GNU Lesser General Public License. + + The Free Software Foundation may publish revised and/or new versions +of the GNU Lesser General Public License from time to time. Such new +versions will be similar in spirit to the present version, but may +differ in detail to address new problems or concerns. + + Each version is given a distinguishing version number. If the +Library as you received it specifies that a certain numbered version +of the GNU Lesser General Public License "or any later version" +applies to it, you have the option of following the terms and +conditions either of that published version or of any later version +published by the Free Software Foundation. If the Library as you +received it does not specify a version number of the GNU Lesser +General Public License, you may choose any version of the GNU Lesser +General Public License ever published by the Free Software Foundation. + + If the Library as you received it specifies that a proxy can decide +whether future versions of the GNU Lesser General Public License shall +apply, that proxy's public statement of acceptance of any version is +permanent authorization for you to choose that version for the +Library. diff --git a/jquery.tagcanvas.js b/jquery.tagcanvas.js deleted file mode 100644 index 0798207..0000000 --- a/jquery.tagcanvas.js +++ /dev/null @@ -1,2469 +0,0 @@ -/** - * Copyright (C) 2010-2021 Graham Breach - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -/** - * jQuery.tagcanvas 2.11 - * For more information, please contact - */ -(function($){ -"use strict"; -var i, j, abs = Math.abs, sin = Math.sin, cos = Math.cos, max = Math.max, - min = Math.min, ceil = Math.ceil, sqrt = Math.sqrt, pow = Math.pow, - hexlookup3 = {}, hexlookup2 = {}, hexlookup1 = { - 0:"0,", 1:"17,", 2:"34,", 3:"51,", 4:"68,", 5:"85,", - 6:"102,", 7:"119,", 8:"136,", 9:"153,", a:"170,", A:"170,", - b:"187,", B:"187,", c:"204,", C:"204,", d:"221,", D:"221,", - e:"238,", E:"238,", f:"255,", F:"255," - }, Oproto, Tproto, TCproto, Mproto, Vproto, TSproto, TCVproto, - doc = document, ocanvas, audio, audioClick, handlers = {}; -for(i = 0; i < 256; ++i) { - j = i.toString(16); - if(i < 16) - j = '0' + j; - hexlookup2[j] = hexlookup2[j.toUpperCase()] = i.toString() + ','; -} -function Defined(d) { - return typeof d != 'undefined'; -} -function IsObject(o) { - return typeof o == 'object' && o != null; -} -function Clamp(v, mn, mx) { - return isNaN(v) ? mx : min(mx, max(mn, v)); -} -function Nop() { - return false; -} -function TimeNow() { - return new Date().valueOf(); -} -function SortList(l, f) { - var nl = [], tl = l.length, i; - for(i = 0; i < tl; ++i) - nl.push(l[i]); - nl.sort(f); - return nl; -} -function Shuffle(a) { - var i = a.length-1, t, p; - while(i) { - p = ~~(Math.random()*i); - t = a[i]; - a[i] = a[p]; - a[p] = t; - --i; - } -} -function SetupAudio() { - var ac = window.AudioContext || window.webkitAudioContext; - audio = new ac(); - if(!audio) { - audio = 'off'; - return; - } - return audio; -} -function AudioIcon(mute,c,size,offsetx,offsety,stroke,colour) { - var x = offsetx, y = offsety, s = size * 0.01, w = 80 * s, h = 100 * s, d = 40 * s, e = 30 * s; - var f = e / 2; - var x2 = x + w, x1 = x2 - d; - var y3 = y + h, y2 = y3 - e, y1 = y + e, y4 = y + h / 2; - c.setTransform(1, 0, 0, 1, 0, 0); - c.setLineDash([]); - c.globalAlpha = 1; - c.strokeStyle = colour; - c.lineWidth = stroke; - c.lineJoin = 'round'; - c.beginPath(); - c.moveTo(x1, y1); - c.lineTo(x1, y2); - c.moveTo(x2, y3); - c.lineTo(x1, y2); - c.lineTo(x, y2); - c.lineTo(x, y1); - c.lineTo(x1, y1); - c.lineTo(x2, y); - if(mute) { - c.lineTo(x2, y1); - c.moveTo(x2, y2); - c.lineTo(x2, y3); - c.moveTo(x2 - f, y4 - f); - c.lineTo(x2 + f, y4 + f); - c.moveTo(x2 + f, y4 - f); - c.lineTo(x2 - f, y4 + f); - c.stroke(); - return; - } - c.closePath(); - c.stroke(); -} -function Vector(x, y, z) { - this.x = x; - this.y = y; - this.z = z; -} -Vproto = Vector.prototype; -Vproto.length = function() { - return sqrt(this.x * this.x + this.y * this.y + this.z * this.z); -}; -Vproto.dot = function(v) { - return this.x * v.x + this.y * v.y + this.z * v.z; -}; -Vproto.cross = function(v) { - var x = this.y * v.z - this.z * v.y, - y = this.z * v.x - this.x * v.z, - z = this.x * v.y - this.y * v.x; - return new Vector(x, y, z); -}; -Vproto.angle = function(v) { - var dot = this.dot(v), ac; - if(dot == 0) - return Math.PI / 2.0; - ac = dot / (this.length() * v.length()); - if(ac >= 1) - return 0; - if(ac <= -1) - return Math.PI; - return Math.acos(ac); -}; -Vproto.unit = function() { - var l = this.length(); - return new Vector(this.x / l, this.y / l, this.z / l); -}; -function MakeVector(lg, lt) { - lt = lt * Math.PI / 180; - lg = lg * Math.PI / 180; - var x = sin(lg) * cos(lt), y = -sin(lt), z = -cos(lg) * cos(lt); - return new Vector(x, y, z); -} -function Matrix(a) { - this[1] = {1: a[0], 2: a[1], 3: a[2]}; - this[2] = {1: a[3], 2: a[4], 3: a[5]}; - this[3] = {1: a[6], 2: a[7], 3: a[8]}; -} -Mproto = Matrix.prototype; -Matrix.Identity = function() { - return new Matrix([1,0,0, 0,1,0, 0,0,1]); -}; -Matrix.Rotation = function(angle, u) { - var sina = sin(angle), cosa = cos(angle), mcos = 1 - cosa; - return new Matrix([ - cosa + pow(u.x, 2) * mcos, u.x * u.y * mcos - u.z * sina, u.x * u.z * mcos + u.y * sina, - u.y * u.x * mcos + u.z * sina, cosa + pow(u.y, 2) * mcos, u.y * u.z * mcos - u.x * sina, - u.z * u.x * mcos - u.y * sina, u.z * u.y * mcos + u.x * sina, cosa + pow(u.z, 2) * mcos - ]); -} -Mproto.mul = function(m) { - var a = [], i, j, mmatrix = (m.xform ? 1 : 0); - for(i = 1; i <= 3; ++i) - for(j = 1; j <= 3; ++j) { - if(mmatrix) - a.push(this[i][1] * m[1][j] + - this[i][2] * m[2][j] + - this[i][3] * m[3][j]); - else - a.push(this[i][j] * m); - } - return new Matrix(a); -}; -Mproto.xform = function(p) { - var a = {}, x = p.x, y = p.y, z = p.z; - a.x = x * this[1][1] + y * this[2][1] + z * this[3][1]; - a.y = x * this[1][2] + y * this[2][2] + z * this[3][2]; - a.z = x * this[1][3] + y * this[2][3] + z * this[3][3]; - return a; -}; -function PointsOnSphere(n,xr,yr,zr,magic) { - var i, y, r, phi, pts = [], off = 2/n, inc; - inc = Math.PI * (3 - sqrt(5) + (parseFloat(magic) ? parseFloat(magic) : 0)); - for(i = 0; i < n; ++i) { - y = i * off - 1 + (off / 2); - r = sqrt(1 - y*y); - phi = i * inc; - pts.push([cos(phi) * r * xr, y * yr, sin(phi) * r * zr]); - } - return pts; -} -function Cylinder(n,o,xr,yr,zr,magic) { - var phi, pts = [], off = 2/n, inc, i, j, k, l; - inc = Math.PI * (3 - sqrt(5) + (parseFloat(magic) ? parseFloat(magic) : 0)); - for(i = 0; i < n; ++i) { - j = i * off - 1 + (off / 2); - phi = i * inc; - k = cos(phi); - l = sin(phi); - pts.push(o ? [j * xr, k * yr, l * zr] : [k * xr, j * yr, l * zr]); - } - return pts; -} -function Ring(o, n, xr, yr, zr, j) { - var phi, pts = [], inc = Math.PI * 2 / n, i, k, l; - for(i = 0; i < n; ++i) { - phi = i * inc; - k = cos(phi); - l = sin(phi); - pts.push(o ? [j * xr, k * yr, l * zr] : [k * xr, j * yr, l * zr]); - } - return pts; -} -function PointsOnCylinderV(n,xr,yr,zr,m) { return Cylinder(n, 0, xr, yr, zr, m) } -function PointsOnCylinderH(n,xr,yr,zr,m) { return Cylinder(n, 1, xr, yr, zr, m) } -function PointsOnRingV(n, xr, yr, zr, offset) { - offset = isNaN(offset) ? 0 : offset * 1; - return Ring(0, n, xr, yr, zr, offset); -} -function PointsOnRingH(n, xr, yr, zr, offset) { - offset = isNaN(offset) ? 0 : offset * 1; - return Ring(1, n, xr, yr, zr, offset); -} -function CentreImage(t) { - var i = new Image; - i.onload = function() { - var dx = i.width / 2, dy = i.height / 2; - t.centreFunc = function(c, w, h, cx, cy) { - c.setTransform(1, 0, 0, 1, 0, 0); - c.globalAlpha = 1; - c.drawImage(i, cx - dx, cy - dy); - }; - }; - i.src = t.centreImage; -} -function SetAlpha(c,a) { - var d = c, p1, p2, ae = (a*1).toPrecision(3) + ')'; - if(c[0] === '#') { - if(!hexlookup3[c]) - if(c.length === 4) - hexlookup3[c] = 'rgba(' + hexlookup1[c[1]] + hexlookup1[c[2]] + hexlookup1[c[3]]; - else - hexlookup3[c] = 'rgba(' + hexlookup2[c.substr(1,2)] + hexlookup2[c.substr(3,2)] + hexlookup2[c.substr(5,2)]; - d = hexlookup3[c] + ae; - } else if(c.substr(0,4) === 'rgb(' || c.substr(0,4) === 'hsl(') { - d = (c.replace('(','a(').replace(')', ',' + ae)); - } else if(c.substr(0,5) === 'rgba(' || c.substr(0,5) === 'hsla(') { - p1 = c.lastIndexOf(',') + 1, p2 = c.indexOf(')'); - a *= parseFloat(c.substring(p1,p2)); - d = c.substr(0,p1) + a.toPrecision(3) + ')'; - } - return d; -} -function NewCanvas(w,h) { - // if using excanvas, give up now - if(window.G_vmlCanvasManager) - return null; - var c = doc.createElement('canvas'); - c.width = w; - c.height = h; - return c; -} -// I think all browsers pass this test now... -function ShadowAlphaBroken() { - var cv = NewCanvas(3,3), c, i; - if(!cv) - return false; - c = cv.getContext('2d'); - c.strokeStyle = '#000'; - c.shadowColor = '#fff'; - c.shadowBlur = 3; - c.globalAlpha = 0; - c.strokeRect(2,2,2,2); - c.globalAlpha = 1; - i = c.getImageData(2,2,1,1); - cv = null; - return (i.data[0] > 0); -} -function SetGradient(c, l, o, g) { - var gd = c.createLinearGradient(0, 0, l, 0), i; - for(i in g) - gd.addColorStop(1 - i, g[i]); - c.fillStyle = gd; - c.fillRect(0, o, l, 1); -} -function FindGradientColour(tc, p, r) { - var l = 1024, h = 1, gl = tc.weightGradient, cv, c, i, d; - if(tc.gCanvas) { - c = tc.gCanvas.getContext('2d'); - h = tc.gCanvas.height; - } else { - if(IsObject(gl[0])) - h = gl.length; - else - gl = [gl]; - tc.gCanvas = cv = NewCanvas(l, h); - if(!cv) - return null; - c = cv.getContext('2d'); - for(i = 0; i < h; ++i) - SetGradient(c, l, i, gl[i]); - } - r = max(min(r || 0, h - 1), 0); - d = c.getImageData(~~((l - 1) * p), r, 1, 1).data; - return 'rgba(' + d[0] + ',' + d[1] + ',' + d[2] + ',' + (d[3]/255) + ')'; -} -function TextSet(ctxt, font, colour, strings, padx, pady, shadowColour, - shadowBlur, shadowOffsets, maxWidth, widths, align) { - var xo = padx + (shadowBlur || 0) + - (shadowOffsets.length && shadowOffsets[0] < 0 ? abs(shadowOffsets[0]) : 0), - yo = pady + (shadowBlur || 0) + - (shadowOffsets.length && shadowOffsets[1] < 0 ? abs(shadowOffsets[1]) : 0), i, xc; - ctxt.font = font; - ctxt.textBaseline = 'top'; - ctxt.fillStyle = colour; - shadowColour && (ctxt.shadowColor = shadowColour); - shadowBlur && (ctxt.shadowBlur = shadowBlur); - shadowOffsets.length && (ctxt.shadowOffsetX = shadowOffsets[0], - ctxt.shadowOffsetY = shadowOffsets[1]); - for(i = 0; i < strings.length; ++i) { - xc = 0; - if(widths) { - if('right' == align) { - xc = maxWidth - widths[i]; - } else if('centre' == align) { - xc = (maxWidth - widths[i]) / 2; - } - } - ctxt.fillText(strings[i], xo + xc, yo); - yo += parseInt(font); - } -} -function RRect(c, x, y, w, h, r, s) { - if(r) { - c.beginPath(); - c.moveTo(x, y + h - r); - c.arcTo(x, y, x + r, y, r); - c.arcTo(x + w, y, x + w, y + r, r); - c.arcTo(x + w, y + h, x + w - r, y + h, r); - c.arcTo(x, y + h, x, y + h - r, r); - c.closePath(); - c[s ? 'stroke' : 'fill'](); - } else { - c[s ? 'strokeRect' : 'fillRect'](x, y, w, h); - } -} -function TextCanvas(strings, font, w, h, maxWidth, stringWidths, align, valign, - scale) { - this.strings = strings; - this.font = font; - this.width = w; - this.height = h; - this.maxWidth = maxWidth; - this.stringWidths = stringWidths; - this.align = align; - this.valign = valign; - this.scale = scale; -} -TCVproto = TextCanvas.prototype; -TCVproto.SetImage = function(image, w, h, position, padding, align, valign, - scale) { - this.image = image; - this.iwidth = w * this.scale; - this.iheight = h * this.scale; - this.ipos = position; - this.ipad = padding * this.scale; - this.iscale = scale; - this.ialign = align; - this.ivalign = valign; -}; -TCVproto.Align = function(size, space, a) { - var pos = 0; - if(a == 'right' || a == 'bottom') - pos = space - size; - else if(a != 'left' && a != 'top') - pos = (space - size) / 2; - return pos; -}; -TCVproto.Create = function(colour, bgColour, bgOutline, bgOutlineThickness, - shadowColour, shadowBlur, shadowOffsets, padding, radius) { - var cv, cw, ch, c, x1, x2, y1, y2, offx, offy, ix, iy, iw, ih, rr, - sox = abs(shadowOffsets[0]), soy = abs(shadowOffsets[1]), shadowcv, shadowc; - padding = max(padding, sox + shadowBlur, soy + shadowBlur); - x1 = 2 * (padding + bgOutlineThickness); - y1 = 2 * (padding + bgOutlineThickness); - cw = this.width + x1; - ch = this.height + y1; - offx = offy = padding + bgOutlineThickness; - - if(this.image) { - ix = iy = padding + bgOutlineThickness; - iw = this.iwidth; - ih = this.iheight; - if(this.ipos == 'top' || this.ipos == 'bottom') { - if(iw < this.width) - ix += this.Align(iw, this.width, this.ialign); - else - offx += this.Align(this.width, iw, this.align); - if(this.ipos == 'top') - offy += ih + this.ipad; - else - iy += this.height + this.ipad; - cw = max(cw, iw + x1); - ch += ih + this.ipad; - } else { - if(ih < this.height) - iy += this.Align(ih, this.height, this.ivalign); - else - offy += this.Align(this.height, ih, this.valign); - if(this.ipos == 'right') - ix += this.width + this.ipad; - else - offx += iw + this.ipad; - cw += iw + this.ipad; - ch = max(ch, ih + y1); - } - } - - cv = NewCanvas(cw, ch); - if(!cv) - return null; - x1 = y1 = bgOutlineThickness / 2; - x2 = cw - bgOutlineThickness; - y2 = ch - bgOutlineThickness; - rr = min(radius, x2 / 2, y2 / 2); - c = cv.getContext('2d'); - if(bgColour) { - c.fillStyle = bgColour; - RRect(c, x1, y1, x2, y2, rr); - } - if(bgOutlineThickness) { - c.strokeStyle = bgOutline; - c.lineWidth = bgOutlineThickness; - RRect(c, x1, y1, x2, y2, rr, true); - } - if(shadowBlur || sox || soy) { - // use a transparent canvas to draw on - shadowcv = NewCanvas(cw, ch); - if(shadowcv) { - shadowc = c; - c = shadowcv.getContext('2d'); - } - } - - // don't use TextSet shadow support because it adds space for shadow - TextSet(c, this.font, colour, this.strings, offx, offy, 0, 0, [], - this.maxWidth, this.stringWidths, this.align); - - if(this.image) - c.drawImage(this.image, ix, iy, iw, ih); - - if(shadowc) { - // draw the text and image with the added shadow - c = shadowc; - shadowColour && (c.shadowColor = shadowColour); - shadowBlur && (c.shadowBlur = shadowBlur); - c.shadowOffsetX = shadowOffsets[0]; - c.shadowOffsetY = shadowOffsets[1]; - c.drawImage(shadowcv, 0, 0); - } - return cv; -}; -function ExpandImage(i, w, h) { - var cv = NewCanvas(w, h), c; - if(!cv) - return null; - c = cv.getContext('2d'); - c.drawImage(i, (w - i.width) / 2, (h - i.height) / 2); - return cv; -} -function ScaleImage(i, w, h) { - var cv = NewCanvas(w, h), c; - if(!cv) - return null; - c = cv.getContext('2d'); - c.drawImage(i, 0, 0, w, h); - return cv; -} -function AddBackgroundToImage(i, w, h, scale, colour, othickness, ocolour, - padding, radius, ofill) { - var cw = w + ((2 * padding) + othickness) * scale, - ch = h + ((2 * padding) + othickness) * scale, - cv = NewCanvas(cw, ch), c, x1, y1, x2, y2, ocanvas, cc, rr; - if(!cv) - return null; - othickness *= scale; - radius *= scale; - x1 = y1 = othickness / 2; - x2 = cw - othickness; - y2 = ch - othickness; - padding = (padding * scale) + x1; // add space for outline - c = cv.getContext('2d'); - rr = min(radius, x2 / 2, y2 / 2); - if(colour) { - c.fillStyle = colour; - RRect(c, x1, y1, x2, y2, rr); - } - if(othickness) { - c.strokeStyle = ocolour; - c.lineWidth = othickness; - RRect(c, x1, y1, x2, y2, rr, true); - } - - if(ofill) { - // use compositing to colour in the image and border - ocanvas = NewCanvas(cw, ch); - cc = ocanvas.getContext('2d'); - cc.drawImage(i, padding, padding, w, h); - cc.globalCompositeOperation = 'source-in'; - cc.fillStyle = ocolour; - cc.fillRect(0, 0, cw, ch); - cc.globalCompositeOperation = 'destination-over'; - cc.drawImage(cv, 0, 0); - cc.globalCompositeOperation = 'source-over'; - c.drawImage(ocanvas, 0, 0); - } else { - c.drawImage(i, padding, padding, i.width, i.height); - } - return {image: cv, width: cw / scale, height: ch / scale}; -} -/** - * Rounds off the corners of an image - */ -function RoundImage(i, r, iw, ih, s) { - var cv, c, r1 = parseFloat(r), l = max(iw, ih); - cv = NewCanvas(iw, ih); - if(!cv) - return null; - if(r.indexOf('%') > 0) - r1 = l * r1 / 100; - else - r1 = r1 * s; - c = cv.getContext('2d'); - c.globalCompositeOperation = 'source-over'; - c.fillStyle = '#fff'; - if(r1 >= l/2) { - r1 = min(iw,ih) / 2; - c.beginPath(); - c.moveTo(iw/2,ih/2); - c.arc(iw/2,ih/2,r1,0,2*Math.PI,false); - c.fill(); - c.closePath(); - } else { - r1 = min(iw/2,ih/2,r1); - RRect(c, 0, 0, iw, ih, r1, true); - c.fill(); - } - c.globalCompositeOperation = 'source-in'; - c.drawImage(i, 0, 0, iw, ih); - return cv; -} -/** - * Creates a new canvas containing the image and its shadow - * Returns an object containing the image and its dimensions at z=0 - */ -function AddShadowToImage(i, w, h, scale, sc, sb, so) { - var sw = abs(so[0]), sh = abs(so[1]), - cw = w + (sw > sb ? sw + sb : sb * 2) * scale, - ch = h + (sh > sb ? sh + sb : sb * 2) * scale, - xo = scale * ((sb || 0) + (so[0] < 0 ? sw : 0)), - yo = scale * ((sb || 0) + (so[1] < 0 ? sh : 0)), cv, c; - cv = NewCanvas(cw, ch); - if(!cv) - return null; - c = cv.getContext('2d'); - sc && (c.shadowColor = sc); - sb && (c.shadowBlur = sb * scale); - so && (c.shadowOffsetX = so[0] * scale, c.shadowOffsetY = so[1] * scale); - c.drawImage(i, xo, yo, w, h); - return {image: cv, width: cw / scale, height: ch / scale}; -} -function FindTextBoundingBox(s,f,ht) { - var w = parseInt(s.toString().length * ht), h = parseInt(ht * 2 * s.length), - cv = NewCanvas(w,h), c, idata, w1, h1, x, y, i, ex; - if(!cv) - return null; - c = cv.getContext('2d'); - c.fillStyle = '#000'; - c.fillRect(0,0,w,h); - TextSet(c,ht + 'px ' + f,'#fff',s,0,0,0,0,[],'centre') - - idata = c.getImageData(0,0,w,h); - w1 = idata.width; h1 = idata.height; - ex = { - min: { x: w1, y: h1 }, - max: { x: -1, y: -1 } - }; - for(y = 0; y < h1; ++y) { - for(x = 0; x < w1; ++x) { - i = (y * w1 + x) * 4; - if(idata.data[i+1] > 0) { - if(x < ex.min.x) ex.min.x = x; - if(x > ex.max.x) ex.max.x = x; - if(y < ex.min.y) ex.min.y = y; - if(y > ex.max.y) ex.max.y = y; - } - } - } - // device pixels might not be css pixels - if(w1 != w) { - ex.min.x *= (w / w1); - ex.max.x *= (w / w1); - } - if(h1 != h) { - ex.min.y *= (w / h1); - ex.max.y *= (w / h1); - } - - cv = null; - return ex; -} -function FixFont(f) { - return "'" + f.replace(/(\'|\")/g,'').replace(/\s*,\s*/g, "', '") + "'"; -} -function AddHandler(h,f,e) { - e = e || doc; - if(e.addEventListener) - e.addEventListener(h,f,false); - else - e.attachEvent('on' + h, f); -} -function RemoveHandler(h,f,e) { - e = e || doc; - if(e.removeEventListener) - e.removeEventListener(h, f); - else - e.detachEvent('on' + h, f); -} -function AddImage(i, o, alt, t, tc) { - var s = tc.imageScale, mscale, ic, bc, oc, iw, ih; - // image not loaded, wait for image onload - if(!o.complete) - return AddHandler('load',function() { AddImage(i,o,alt,t,tc); }, o); - if(!i.complete) - return AddHandler('load',function() { AddImage(i,o,alt,t,tc); }, i); - if(alt && !alt.complete) - return AddHandler('load',function() { AddImage(i,o,alt,t,tc); }, alt); - - // Yes, this does look like nonsense, but it makes sure that both the - // width and height are actually set and not just calculated. This is - // required to keep proportional sizes when the images are hidden, so - // the images can be used again for another cloud. - o.width = o.width; - o.height = o.height; - - if(s) { - i.width = o.width * s; - i.height = o.height * s; - } - // the standard width of the image, with imageScale applied - t.iw = i.width; - t.ih = i.height; - if(tc.txtOpt) { - ic = i; - mscale = tc.zoomMax * tc.txtScale; - iw = t.iw * mscale; - ih = t.ih * mscale; - if(iw < o.naturalWidth || ih < o.naturalHeight) { - ic = ScaleImage(i, iw, ih); - if(ic) - t.fimage = ic; - } else { - iw = t.iw; - ih = t.ih; - mscale = 1; - } - if(parseFloat(tc.imageRadius)) - t.image = t.fimage = i = RoundImage(t.image, tc.imageRadius, iw, ih, mscale); - if(!t.HasText()) { - if(tc.shadow) { - ic = AddShadowToImage(t.image, iw, ih, mscale, tc.shadow, tc.shadowBlur, - tc.shadowOffset); - if(ic) { - t.fimage = ic.image; - t.w = ic.width; - t.h = ic.height; - } - } - if(tc.bgColour || tc.bgOutlineThickness) { - bc = tc.bgColour == 'tag' ? GetProperty(t.a, 'background-color') : - tc.bgColour; - oc = tc.bgOutline == 'tag' ? GetProperty(t.a, 'color') : - (tc.bgOutline || tc.textColour); - iw = t.fimage.width; - ih = t.fimage.height; - if(tc.outlineMethod == 'colour') { - // create the outline version first, using the current image state - ic = AddBackgroundToImage(t.fimage, iw, ih, mscale, bc, - tc.bgOutlineThickness, t.outline.colour, tc.padding, tc.bgRadius, 1); - if(ic) - t.oimage = ic.image; - } - ic = AddBackgroundToImage(t.fimage, iw, ih, mscale, bc, - tc.bgOutlineThickness, oc, tc.padding, tc.bgRadius); - if(ic) { - t.fimage = ic.image; - t.w = ic.width; - t.h = ic.height; - } - } - if(tc.outlineMethod == 'size') { - if(tc.outlineIncrease > 0) { - t.iw += 2 * tc.outlineIncrease; - t.ih += 2 * tc.outlineIncrease; - iw = mscale * t.iw; - ih = mscale * t.ih; - ic = ScaleImage(t.fimage, iw, ih); - t.oimage = ic; - t.fimage = ExpandImage(t.fimage, t.oimage.width, t.oimage.height); - } else { - iw = mscale * (t.iw + (2 * tc.outlineIncrease)); - ih = mscale * (t.ih + (2 * tc.outlineIncrease)); - ic = ScaleImage(t.fimage, iw, ih); - t.oimage = ExpandImage(ic, t.fimage.width, t.fimage.height); - } - } - } - } - t.alt = alt; - t.Init(); -} -function GetProperty(e,p) { - var dv = doc.defaultView, pc = p.replace(/\-([a-z])/g,function(a){return a.charAt(1).toUpperCase()}); - return (dv && dv.getComputedStyle && dv.getComputedStyle(e,null).getPropertyValue(p)) || - (e.currentStyle && e.currentStyle[pc]); -} -function FindWeight(a, wFrom, tHeight) { - var w = 1, p; - if(wFrom) { - w = 1 * (a.getAttribute(wFrom) || tHeight); - } else if(p = GetProperty(a,'font-size')) { - w = (p.indexOf('px') > -1 && p.replace('px','') * 1) || - (p.indexOf('pt') > -1 && p.replace('pt','') * 1.25) || - p * 3.3; - } - return w; -} -function EventToCanvasId(e) { - return e.target && Defined(e.target.id) ? e.target.id : - e.srcElement.parentNode.id; -} -function EventXY(e, c) { - var xy, p, xmul = parseInt(GetProperty(c, 'width')) / c.width, - ymul = parseInt(GetProperty(c, 'height')) / c.height; - if(Defined(e.offsetX)) { - xy = {x: e.offsetX, y: e.offsetY}; - } else { - p = AbsPos(c.id); - if(Defined(e.changedTouches)) - e = e.changedTouches[0]; - if(e.pageX) - xy = {x: e.pageX - p.x, y: e.pageY - p.y}; - } - if(xy && xmul && ymul) { - xy.x /= xmul; - xy.y /= ymul; - } - return xy; -} -function MouseOut(e) { - var cv = e.target || e.fromElement.parentNode, tc = TagCanvas.tc[cv.id]; - if(tc) { - tc.mx = tc.my = -1; - tc.UnFreeze(); - tc.EndDrag(); - } -} -function MouseMove(e) { - var i, t = TagCanvas, tc, p, tg = EventToCanvasId(e); - for(i in t.tc) { - tc = t.tc[i]; - if(tc.tttimer) { - clearTimeout(tc.tttimer); - tc.tttimer = null; - } - } - if(tg && t.tc[tg]) { - tc = t.tc[tg]; - if(p = EventXY(e, tc.canvas)) { - tc.mx = p.x; - tc.my = p.y; - tc.Drag(e, p); - } - tc.drawn = 0; - } -} -function MouseDown(e) { - var t = TagCanvas, cb = doc.addEventListener ? 0 : 1, - tg = EventToCanvasId(e); - if(tg && e.button == cb && t.tc[tg]) { - t.tc[tg].BeginDrag(e); - } -} -function MouseUp(e) { - var t = TagCanvas, cb = doc.addEventListener ? 0 : 1, - tg = EventToCanvasId(e), tc; - if(tg && e.button == cb && t.tc[tg]) { - tc = t.tc[tg]; - MouseMove(e); - if(!tc.EndDrag() && !tc.touchState) - tc.Clicked(e); - } -} -function TouchDown(e) { - var tg = EventToCanvasId(e), tc = (tg && TagCanvas.tc[tg]), p; - if(tc && e.changedTouches) { - if(e.touches.length == 1 && tc.touchState == 0) { - tc.touchState = 1; - tc.BeginDrag(e); - if(p = EventXY(e, tc.canvas)) { - tc.mx = p.x; - tc.my = p.y; - tc.drawn = 0; - } - } else if(e.targetTouches.length == 2 && tc.pinchZoom) { - tc.touchState = 3; - tc.EndDrag(); - tc.BeginPinch(e); - } else { - tc.EndDrag(); - tc.EndPinch(); - tc.touchState = 0; - } - } -} -function TouchUp(e) { - var tg = EventToCanvasId(e), tc = (tg && TagCanvas.tc[tg]); - if(tc && e.changedTouches) { - switch(tc.touchState) { - case 1: - tc.Draw(); - tc.Clicked(); - break; - case 2: - tc.EndDrag(); - break; - case 3: - tc.EndPinch(); - } - tc.touchState = 0; - } -} -function TouchMove(e) { - var i, t = TagCanvas, tc, p, tg = EventToCanvasId(e); - for(i in t.tc) { - tc = t.tc[i]; - if(tc.tttimer) { - clearTimeout(tc.tttimer); - tc.tttimer = null; - } - } - tc = (tg && t.tc[tg]); - if(tc && e.changedTouches && tc.touchState) { - switch(tc.touchState) { - case 1: - case 2: - if(p = EventXY(e, tc.canvas)) { - tc.mx = p.x; - tc.my = p.y; - if(tc.Drag(e, p)) - tc.touchState = 2; - } - break; - case 3: - tc.Pinch(e); - } - tc.drawn = 0; - } -} -function MouseWheel(e) { - var t = TagCanvas, tg = EventToCanvasId(e); - if(tg && t.tc[tg]) { - e.cancelBubble = true; - e.returnValue = false; - e.preventDefault && e.preventDefault(); - t.tc[tg].Wheel((e.wheelDelta || e.detail) > 0); - } -} -function Scroll(e) { - var i, t = TagCanvas; - clearTimeout(t.scrollTimer); - for(i in t.tc) { - t.tc[i].Pause(); - } - t.scrollTimer = setTimeout(function() { - var i, t = TagCanvas; - for(i in t.tc) { - t.tc[i].Resume(); - } - }, t.scrollPause); -} -function DrawCanvas() { - DrawCanvasRAF(TimeNow()); -} -function DrawCanvasRAF(t) { - var tc = TagCanvas.tc, i; - TagCanvas.NextFrame(TagCanvas.interval); - t = t || TimeNow(); - for(i in tc) - tc[i].Draw(t); -} -function NextFrameRAF() { - requestAnimationFrame(DrawCanvasRAF); -}; -function NextFrameTimeout(iv) { - setTimeout(DrawCanvas, iv); -}; -function AbsPos(id) { - var e = doc.getElementById(id), r = e.getBoundingClientRect(), - dd = doc.documentElement, b = doc.body, w = window, - xs = w.pageXOffset || dd.scrollLeft, - ys = w.pageYOffset || dd.scrollTop, - xo = dd.clientLeft || b.clientLeft, - yo = dd.clientTop || b.clientTop; - return { x: r.left + xs - xo, y: r.top + ys - yo }; -} -function Project(tc,p1,sx,sy) { - var m = tc.radius * tc.z1 / (tc.z1 + tc.z2 + p1.z); - return { - x: p1.x * m * sx, - y: p1.y * m * sy, - z: p1.z, - w: (tc.z1 - p1.z) / tc.z2 - }; -} -/** - * @constructor - * for recursively splitting tag contents on
tags - */ -function TextSplitter(e) { - this.e = e; - this.br = 0; - this.line = []; - this.text = []; - this.original = e.innerText || e.textContent; -} -TSproto = TextSplitter.prototype; -TSproto.Empty = function() { - for(var i = 0; i < this.text.length; ++i) - if(this.text[i].length) - return false; - return true; -}; -TSproto.Lines = function(e) { - var r = e ? 1 : 0, cn, cl, i; - e = e || this.e; - cn = e.childNodes; - cl = cn.length; - - for(i = 0; i < cl; ++i) { - if(cn[i].nodeName == 'BR') { - this.text.push(this.line.join(' ')); - this.br = 1; - } else if(cn[i].nodeType == 3) { - if(this.br) { - this.line = [cn[i].nodeValue]; - this.br = 0; - } else { - this.line.push(cn[i].nodeValue); - } - } else { - this.Lines(cn[i]); - } - } - r || this.br || this.text.push(this.line.join(' ')); - return this.text; -}; -TSproto.SplitWidth = function(w, c, f, h) { - var i, j, words, text = []; - c.font = h + 'px ' + f; - for(i = 0; i < this.text.length; ++i) { - words = this.text[i].split(/\s+/); - this.line = [words[0]]; - for(j = 1; j < words.length; ++j) { - if(c.measureText(this.line.join(' ') + ' ' + words[j]).width > w) { - text.push(this.line.join(' ')); - this.line = [words[j]]; - } else { - this.line.push(words[j]); - } - } - text.push(this.line.join(' ')); - } - return this.text = text; -}; -/** - * @constructor - */ -function Outline(tc,t) { - this.ts = null; - this.tc = tc; - this.tag = t; - this.x = this.y = this.w = this.h = this.sc = 1; - this.z = 0; - this.pulse = 1; - this.pulsate = tc.pulsateTo < 1; - this.colour = tc.outlineColour; - this.adash = ~~tc.outlineDash; - this.agap = ~~tc.outlineDashSpace || this.adash; - this.aspeed = tc.outlineDashSpeed * 1; - if(this.colour == 'tag') - this.colour = GetProperty(t.a, 'color'); - else if(this.colour == 'tagbg') - this.colour = GetProperty(t.a, 'background-color'); - this.Draw = this.pulsate ? this.DrawPulsate : this.DrawSimple; - this.radius = tc.outlineRadius | 0; - this.SetMethod(tc.outlineMethod,tc.altImage); -} -Oproto = Outline.prototype; -Oproto.SetMethod = function(om,alt) { - var methods = { - block: ['PreDraw','DrawBlock'], - colour: ['PreDraw','DrawColour'], - outline: ['PostDraw','DrawOutline'], - classic: ['LastDraw','DrawOutline'], - size: ['PreDraw','DrawSize'], - none: ['LastDraw'] - }, funcs = methods[om] || methods.outline; - if(om == 'none') { - this.Draw = function() { return 1; } - } else { - this.drawFunc = this[funcs[1]]; - } - this[funcs[0]] = this.Draw; - if(alt) { - this.RealPreDraw = this.PreDraw; - this.PreDraw = this.DrawAlt; - } -}; -Oproto.Update = function(x,y,w,h,sc,z,xo,yo) { - var o = this.tc.outlineOffset, o2 = 2 * o; - this.x = sc * x + xo - o; - this.y = sc * y + yo - o; - this.w = sc * w + o2; - this.h = sc * h + o2; - this.sc = sc; // used to determine frontmost - this.z = z; -}; -Oproto.Ants = function(c) { - if(!this.adash) - return; - var l = this.adash, g = this.agap, s = this.aspeed, length = l + g, - l1 = 0, l2 = l, g1 = g, g2 = 0, seq = 0, ants; - if(s) { - seq = abs(s) * (TimeNow() - this.ts) / 50; - if(s < 0) - seq = 8.64e6 - seq; - s = ~~seq % length; - } - if(s) { - if(l >= s) { - l1 = l - s; - l2 = s; - } else { - g1 = length - s; - g2 = g - g1; - } - ants = [l1, g1, l2, g2]; - } else { - ants = [l,g]; - } - c.setLineDash(ants); -} -Oproto.DrawOutline = function(c,x,y,w,h,colour) { - var r = min(this.radius, h/2, w/2); - c.strokeStyle = colour; - this.Ants(c); - RRect(c, x, y, w, h, r, true); -}; -Oproto.DrawSize = function(c,x,y,w,h,colour,tag,x1,y1) { - var tw = tag.w, th = tag.h, m, i, sc; - if(this.pulsate) { - if(tag.image) - sc = (tag.image.height + this.tc.outlineIncrease) / tag.image.height; - else - sc = tag.oscale; - i = tag.fimage || tag.image; - m = 1 + ((sc - 1) * (1-this.pulse)); - tag.h *= m; - tag.w *= m; - } else { - i = tag.oimage; - } - tag.alpha = 1; - tag.Draw(c, x1, y1, i); - tag.h = th; - tag.w = tw; - return 1; -}; -Oproto.DrawColour = function(c,x,y,w,h,colour,tag,x1,y1) { - if(tag.oimage) { - if(this.pulse < 1) { - tag.alpha = 1 - pow(this.pulse, 2); - tag.Draw(c, x1, y1, tag.fimage); - tag.alpha = this.pulse; - } else { - tag.alpha = 1; - } - tag.Draw(c, x1, y1, tag.oimage); - return 1; - } - return this[tag.image ? 'DrawColourImage' : 'DrawColourText'](c,x,y,w,h,colour,tag,x1,y1); -}; -Oproto.DrawColourText = function(c,x,y,w,h,colour,tag,x1,y1) { - var normal = tag.colour; - tag.colour = colour; - tag.alpha = 1; - tag.Draw(c,x1,y1); - tag.colour = normal; - return 1; -}; -Oproto.DrawColourImage = function(c,x,y,w,h,colour,tag,x1,y1) { - var ccanvas = c.canvas, fx = ~~max(x,0), fy = ~~max(y,0), - fw = min(ccanvas.width - fx, w) + .5|0, fh = min(ccanvas.height - fy,h) + .5|0, cc; - if(ocanvas) - ocanvas.width = fw, ocanvas.height = fh; - else - ocanvas = NewCanvas(fw, fh); - if(!ocanvas) - return this.SetMethod('outline'); // if using IE and images, give up! - cc = ocanvas.getContext('2d'); - - cc.drawImage(ccanvas,fx,fy,fw,fh,0,0,fw,fh); - c.clearRect(fx,fy,fw,fh); - if(this.pulsate) { - tag.alpha = 1 - pow(this.pulse, 2); - } else { - tag.alpha = 1; - } - tag.Draw(c,x1,y1); - c.setTransform(1,0,0,1,0,0); - c.save(); - c.beginPath(); - c.rect(fx,fy,fw,fh); - c.clip(); - c.globalCompositeOperation = 'source-in'; - c.fillStyle = colour; - c.fillRect(fx,fy,fw,fh); - c.restore(); - c.globalAlpha = 1; - c.globalCompositeOperation = 'destination-over'; - c.drawImage(ocanvas,0,0,fw,fh,fx,fy,fw,fh); - c.globalCompositeOperation = 'source-over'; - return 1; -}; -Oproto.DrawAlt = function(c,tag,x1,y1,ga,useGa) { - var r = this.RealPreDraw(c,tag,x1,y1,ga,useGa); - if(tag.alt) { - tag.DrawImage(c, x1, y1, tag.alt); - r = 1; - } - return r; -}; -Oproto.DrawBlock = function(c,x,y,w,h,colour) { - var r = min(this.radius, h/2, w/2); - c.fillStyle = colour; - RRect(c, x, y, w, h, r); -}; -Oproto.DrawSimple = function(c, tag, x1, y1, ga, useGa) { - var t = this.tc; - c.setTransform(1,0,0,1,0,0); - c.strokeStyle = this.colour; - c.lineWidth = t.outlineThickness; - c.shadowBlur = c.shadowOffsetX = c.shadowOffsetY = 0; - c.globalAlpha = useGa ? ga : 1; - return this.drawFunc(c,this.x,this.y,this.w,this.h,this.colour,tag,x1,y1); -}; -Oproto.DrawPulsate = function(c, tag, x1, y1) { - var diff = TimeNow() - this.ts, t = this.tc, - ga = t.pulsateTo + ((1 - t.pulsateTo) * - (0.5 + (cos(2 * Math.PI * diff / (1000 * t.pulsateTime)) / 2))); - this.pulse = ga = TagCanvas.Smooth(1,ga); - return this.DrawSimple(c, tag, x1, y1, ga, 1); -}; -Oproto.Active = function(c,x,y) { - var a = (x >= this.x && y >= this.y && - x <= this.x + this.w && y <= this.y + this.h); - if(a) { - this.ts = this.ts || TimeNow(); - } else { - this.ts = null; - } - return a; -}; -Oproto.PreDraw = Oproto.PostDraw = Oproto.LastDraw = Nop; -/** - * @constructor - */ -function Tag(tc, text, a, v, w, h, col, bcol, bradius, boutline, bothickness, - font, padding, original) { - this.tc = tc; - this.image = null; - this.text = text; - this.text_original = original; - this.line_widths = []; - this.title = a.title || null; - this.a = a; - this.position = new Vector(v[0], v[1], v[2]); - this.x = this.y = this.z = 0; - this.w = w; - this.h = h; - this.colour = col || tc.textColour; - this.bgColour = bcol || tc.bgColour; - this.bgRadius = bradius | 0; - this.bgOutline = boutline || this.colour; - this.bgOutlineThickness = bothickness | 0; - this.textFont = font || tc.textFont; - this.padding = padding | 0; - this.sc = this.alpha = 1; - this.weighted = !tc.weight; - this.outline = new Outline(tc,this); - this.audio = null; -} -Tproto = Tag.prototype; -Tproto.Init = function(e) { - var tc = this.tc; - this.textHeight = tc.textHeight; - if(this.HasText()) { - this.Measure(tc.ctxt,tc); - } else { - this.w = this.iw; - this.h = this.ih; - } - - this.SetShadowColour = tc.shadowAlpha ? this.SetShadowColourAlpha : this.SetShadowColourFixed; - this.SetDraw(tc); -}; -Tproto.Draw = Nop; -Tproto.HasText = function() { - return this.text && this.text[0].length > 0; -}; -Tproto.EqualTo = function(e) { - var i = e.getElementsByTagName('img'); - if(this.a.href != e.href) - return 0; - if(i.length) - return this.image.src == i[0].src; - return (e.innerText || e.textContent) == this.text_original; -}; -Tproto.SetImage = function(i) { - this.image = this.fimage = i; -}; -Tproto.SetAudio = function(a) { - this.audio = a; - this.audio.load(); -}; -Tproto.SetDraw = function(t) { - this.Draw = this.fimage ? (t.ie > 7 ? this.DrawImageIE : this.DrawImage) : this.DrawText; - t.noSelect && (this.CheckActive = Nop); -}; -Tproto.MeasureText = function(c) { - var i, l = this.text.length, w = 0, wl; - for(i = 0; i < l; ++i) { - this.line_widths[i] = wl = c.measureText(this.text[i]).width; - w = max(w, wl); - } - return w; -}; -Tproto.Measure = function(c,t) { - var extents = FindTextBoundingBox(this.text, this.textFont, this.textHeight), - s, th, f, soff, cw, twidth, theight, img, tcv; - // add the gap at the top to the height to make equal gap at bottom - theight = extents ? extents.max.y + extents.min.y : this.textHeight; - c.font = this.font = this.textHeight + 'px ' + this.textFont; - twidth = this.MeasureText(c); - if(t.txtOpt) { - s = t.txtScale; - th = s * this.textHeight; - f = th + 'px ' + this.textFont; - soff = [s * t.shadowOffset[0], s * t.shadowOffset[1]]; - c.font = f; - cw = this.MeasureText(c); - tcv = new TextCanvas(this.text, f, cw + s, (s * theight) + s, cw, - this.line_widths, t.textAlign, t.textVAlign, s); - - if(this.image) - tcv.SetImage(this.image, this.iw, this.ih, t.imagePosition, t.imagePadding, - t.imageAlign, t.imageVAlign, t.imageScale); - - img = tcv.Create(this.colour, this.bgColour, this.bgOutline, - s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, - s * this.padding, s * this.bgRadius); - - // add outline image using highlight colour - if(t.outlineMethod == 'colour') { - this.oimage = tcv.Create(this.outline.colour, this.bgColour, this.outline.colour, - s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, - s * this.padding, s * this.bgRadius); - - } else if(t.outlineMethod == 'size') { - extents = FindTextBoundingBox(this.text, this.textFont, - this.textHeight + t.outlineIncrease); - th = extents.max.y + extents.min.y; - f = (s * (this.textHeight + t.outlineIncrease)) + 'px ' + this.textFont; - c.font = f; - cw = this.MeasureText(c); - - tcv = new TextCanvas(this.text, f, cw + s, (s * th) + s, cw, - this.line_widths, t.textAlign, t.textVAlign, s); - if(this.image) - tcv.SetImage(this.image, this.iw + t.outlineIncrease, - this.ih + t.outlineIncrease, t.imagePosition, t.imagePadding, - t.imageAlign, t.imageVAlign, t.imageScale); - - this.oimage = tcv.Create(this.colour, this.bgColour, this.bgOutline, - s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, - s * this.padding, s * this.bgRadius); - - this.oscale = this.oimage.width / img.width; - if(t.outlineIncrease > 0) - img = ExpandImage(img, this.oimage.width, this.oimage.height); - else - this.oimage = ExpandImage(this.oimage, img.width, img.height); - } - if(img) { - this.fimage = img; - twidth = this.fimage.width / s; - theight = this.fimage.height / s; - } - this.SetDraw(t); - t.txtOpt = !!this.fimage; - } - this.h = theight; - this.w = twidth; -}; -Tproto.SetFont = function(f, c, bc, boc) { - this.textFont = f; - this.colour = c; - this.bgColour = bc; - this.bgOutline = boc; - this.Measure(this.tc.ctxt, this.tc); -}; -Tproto.SetWeight = function(w) { - var tc = this.tc, modes = tc.weightMode.split(/[, ]/), m, s, wl = w.length; - if(!this.HasText()) - return; - this.weighted = true; - for(s = 0; s < wl; ++s) { - m = modes[s] || 'size'; - if('both' == m) { - this.Weight(w[s], tc.ctxt, tc, 'size', tc.min_weight[s], - tc.max_weight[s], s); - this.Weight(w[s], tc.ctxt, tc, 'colour', tc.min_weight[s], - tc.max_weight[s], s); - } else { - this.Weight(w[s], tc.ctxt, tc, m, tc.min_weight[s], tc.max_weight[s], s); - } - } - this.Measure(tc.ctxt, tc); -}; -Tproto.Weight = function(w, c, t, m, wmin, wmax, wnum) { - w = isNaN(w) ? 1 : w; - var nweight = (w - wmin) / (wmax - wmin); - if('colour' == m) - this.colour = FindGradientColour(t, nweight, wnum); - else if('bgcolour' == m) - this.bgColour = FindGradientColour(t, nweight, wnum); - else if('bgoutline' == m) - this.bgOutline = FindGradientColour(t, nweight, wnum); - else if('outline' == m) - this.outline.colour = FindGradientColour(t, nweight, wnum); - else if('size' == m) { - if(t.weightSizeMin > 0 && t.weightSizeMax > t.weightSizeMin) { - this.textHeight = t.weightSize * - (t.weightSizeMin + (t.weightSizeMax - t.weightSizeMin) * nweight); - } else { - // min textHeight of 1 - this.textHeight = max(1, w * t.weightSize); - } - } -}; -Tproto.SetShadowColourFixed = function(c,s,a) { - c.shadowColor = s; -}; -Tproto.SetShadowColourAlpha = function(c,s,a) { - c.shadowColor = SetAlpha(s, a); -}; -Tproto.DrawText = function(c,xoff,yoff) { - var t = this.tc, x = this.x, y = this.y, s = this.sc, i, xl; - c.globalAlpha = this.alpha; - c.fillStyle = this.colour; - t.shadow && this.SetShadowColour(c,t.shadow,this.alpha); - c.font = this.font; - x += xoff / s; - y += (yoff / s) - (this.h / 2); - for(i = 0; i < this.text.length; ++i) { - xl = x; - if('right' == t.textAlign) { - xl += this.w / 2 - this.line_widths[i]; - } else if('centre' == t.textAlign) { - xl -= this.line_widths[i] / 2; - } else { - xl -= this.w / 2; - } - c.setTransform(s, 0, 0, s, s * xl, s * y); - c.fillText(this.text[i], 0, 0); - y += this.textHeight; - } -}; -Tproto.DrawImage = function(c,xoff,yoff,im) { - var x = this.x, y = this.y, s = this.sc, - i = im || this.fimage, w = this.w, h = this.h, a = this.alpha, - shadow = this.shadow; - c.globalAlpha = a; - shadow && this.SetShadowColour(c,shadow,a); - x += (xoff / s) - (w / 2); - y += (yoff / s) - (h / 2); - c.setTransform(s, 0, 0, s, s * x, s * y); - c.drawImage(i, 0, 0, w, h); -}; -Tproto.DrawImageIE = function(c,xoff,yoff) { - var i = this.fimage, s = this.sc, - w = i.width = this.w*s, h = i.height = this.h * s, - x = (this.x*s) + xoff - (w/2), y = (this.y*s) + yoff - (h/2); - c.setTransform(1,0,0,1,0,0); - c.globalAlpha = this.alpha; - c.drawImage(i, x, y); -}; -Tproto.Calc = function(m,a) { - var pp, t = this.tc, mnb = t.minBrightness, - mxb = t.maxBrightness, r = t.max_radius; - pp = m.xform(this.position); - this.xformed = pp; - pp = Project(t, pp, t.stretchX, t.stretchY); - this.x = pp.x; - this.y = pp.y; - this.z = pp.z; - this.sc = pp.w; - this.alpha = a * Clamp(mnb + (mxb - mnb) * (r - this.z) / (2 * r), 0, 1); - return this.xformed; -}; -Tproto.UpdateActive = function(c, xoff, yoff) { - var o = this.outline, w = this.w, h = this.h, - x = this.x - w/2, y = this.y - h/2; - o.Update(x, y, w, h, this.sc, this.z, xoff, yoff); - return o; -}; -Tproto.CheckActive = function(c,xoff,yoff) { - var t = this.tc, o = this.UpdateActive(c, xoff, yoff); - return o.Active(c, t.mx, t.my) ? o : null; -}; -Tproto.Clicked = function(e) { - var a = this.a, t = a.target, h = a.href, evt; - if(t != '' && t != '_self') { - if(self.frames[t]) { - self.frames[t].document.location = h; - } else{ - try { - if(top.frames[t]) { - top.frames[t].document.location = h; - return; - } - } catch(err) { - // different domain/port/protocol? - } - window.open(h, t); - } - return; - } - if(doc.createEvent) { - evt = doc.createEvent('MouseEvents'); - evt.initMouseEvent('click', 1, 1, window, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null); - if(!a.dispatchEvent(evt)) - return; - } else if(a.fireEvent) { - if(!a.fireEvent('onclick')) - return; - } - doc.location = h; -}; -Tproto.StopAudio = function() { - this.audio && this.playing && this.audio.pause(); - this.stopped = 1; - this.playing = 0; -}; -Tproto.PlayAudio = function() { - if(audio === 'off' || this.tc.audioOff) - return; - if(!audio && !SetupAudio()) - return; - - var a = this.tc.audio, g = this.tc.gain, sus = 'suspended', p; - if(this.audio) { - if(!this.track) { - this.track = audio.createMediaElementSource(this.audio); - this.gain = audio.createGain(); - this.track.connect(this.gain); - this.gain.connect(audio.destination); - } - a = this.audio; - g = this.gain; - if(!a.paused) - return 1; - } - - if(a) { - if(audio.state == sus) - audio.resume(); - if(audio.state == sus) - return; - - g.gain.value = min(2, max(0, this.tc.audioVolume * 1)); - a.currentTime = 0; - this.stopped = 0; - p = a.play(); - if(p !== undefined) { - p.then(r => { - this.stopped ? this.audio.pause() : this.playing = 1; - }); - } - return 1; - } -}; -/** - * @constructor - */ -function TagCanvas(cid,lctr,opt) { - var i, p, c = doc.getElementById(cid), cp = ['id','class','innerHTML']; - - if(!c) throw 0; - if(Defined(window.G_vmlCanvasManager)) { - c = window.G_vmlCanvasManager.initElement(c); - this.ie = parseFloat(navigator.appVersion.split('MSIE')[1]); - } - if(c && (!c.getContext || !c.getContext('2d').fillText)) { - p = doc.createElement('DIV'); - for(i = 0; i < cp.length; ++i) - p[cp[i]] = c[cp[i]]; - c.parentNode.insertBefore(p,c); - c.parentNode.removeChild(c); - throw 0; - } - for(i in TagCanvas.options) - this[i] = opt && Defined(opt[i]) ? opt[i] : - (Defined(TagCanvas[i]) ? TagCanvas[i] : TagCanvas.options[i]); - - this.canvas = c; - this.ctxt = c.getContext('2d'); - this.z1 = 250 / max(this.depth, 0.001); - this.z2 = this.z1 / this.zoom; - this.radius = min(c.height, c.width) * 0.0075; // fits radius of 100 in canvas - this.max_radius = 100; - this.max_weight = []; - this.min_weight = []; - this.textFont = this.textFont && FixFont(this.textFont); - this.textHeight *= 1; - this.imageRadius = this.imageRadius.toString(); - this.pulsateTo = Clamp(this.pulsateTo, 0, 1); - this.minBrightness = Clamp(this.minBrightness, 0, 1); - this.maxBrightness = Clamp(this.maxBrightness, this.minBrightness, 1); - this.ctxt.textBaseline = 'top'; - this.lx = (this.lock + '').indexOf('x') + 1; - this.ly = (this.lock + '').indexOf('y') + 1; - this.frozen = this.dx = this.dy = this.fixedAnim = this.touchState = 0; - this.fixedAlpha = 1; - this.source = lctr || cid; - this.repeatTags = min(64, ~~this.repeatTags); - this.minTags = min(200, ~~this.minTags); - if(~~this.scrollPause > 0) - TagCanvas.scrollPause = ~~this.scrollPause; - else - this.scrollPause = 0; - if(this.minTags > 0 && this.repeatTags < 1 && (i = this.GetTags().length)) - this.repeatTags = ceil(this.minTags / i) - 1; - this.transform = Matrix.Identity(); - this.startTime = this.time = TimeNow(); - this.mx = this.my = -1; - this.centreImage && CentreImage(this); - this.Animate = this.dragControl ? this.AnimateDrag : this.AnimatePosition; - this.animTiming = (typeof TagCanvas[this.animTiming] == 'function' ? - TagCanvas[this.animTiming] : TagCanvas.Smooth); - if(this.shadowBlur || this.shadowOffset[0] || this.shadowOffset[1]) { - // let the browser translate "red" into "#ff0000" - this.ctxt.shadowColor = this.shadow; - this.shadow = this.ctxt.shadowColor; - this.shadowAlpha = ShadowAlphaBroken(); - } else { - delete this.shadow; - } - if(this.activeAudio === false) { - audio = 'off'; - } else { - this.activeAudio && this.LoadAudio(); - } - this.Load(); - if(lctr && this.hideTags) { - (function(t) { - if(TagCanvas.loaded) - t.HideTags(); - else - AddHandler('load', function() { t.HideTags(); }, window); - })(this); - } - - this.yaw = this.initial ? this.initial[0] * this.maxSpeed : 0; - this.pitch = this.initial ? this.initial[1] * this.maxSpeed : 0; - if(this.tooltip) { - this.ctitle = c.title; - c.title = ''; - if(this.tooltip == 'native') { - this.Tooltip = this.TooltipNative; - } else { - this.Tooltip = this.TooltipDiv; - if(!this.ttdiv) { - this.ttdiv = doc.createElement('div'); - this.ttdiv.className = this.tooltipClass; - this.ttdiv.style.position = 'absolute'; - this.ttdiv.style.zIndex = c.style.zIndex + 1; - AddHandler('mouseover',function(e){e.target.style.display='none';},this.ttdiv); - doc.body.appendChild(this.ttdiv); - } - } - } else { - this.Tooltip = this.TooltipNone; - } - if(!this.noMouse && !handlers[cid]) { - handlers[cid] = [ - ['mousemove', MouseMove], - ['mouseout', MouseOut], - ['mouseup', MouseUp], - ['touchstart', TouchDown], - ['touchend', TouchUp], - ['touchcancel', TouchUp], - ['touchmove', TouchMove] - ]; - if(this.dragControl) { - handlers[cid].push(['mousedown', MouseDown]); - handlers[cid].push(['selectstart', Nop]); - } - if(this.wheelZoom) { - handlers[cid].push(['mousewheel', MouseWheel]); - handlers[cid].push(['DOMMouseScroll', MouseWheel]); - } - if(this.scrollPause) { - handlers[cid].push(['scroll', Scroll, window]); - } - for(i = 0; i < handlers[cid].length; ++i) { - p = handlers[cid][i]; - AddHandler(p[0], p[1], p[2] ? p[2] : c); - } - } - if(!TagCanvas.started) { - TagCanvas.NextFrame = window.requestAnimationFrame ? NextFrameRAF : - NextFrameTimeout; - TagCanvas.interval = this.interval; - TagCanvas.NextFrame(this.interval); - TagCanvas.started = 1; - } -} -TCproto = TagCanvas.prototype; -TCproto.SourceElements = function() { - if(doc.querySelectorAll) - return doc.querySelectorAll('#' + this.source); - return [doc.getElementById(this.source)]; -}; -TCproto.HideTags = function() { - var el = this.SourceElements(), i; - for(i = 0; i < el.length; ++i) - el[i].style.display = 'none'; -}; -TCproto.GetTags = function() { - var el = this.SourceElements(), etl, tl = [], i, j, k; - for(k = 0; k <= this.repeatTags; ++k) { - for(i = 0; i < el.length; ++i) { - etl = el[i].getElementsByTagName('a'); - for(j = 0; j < etl.length; ++j) { - tl.push(etl[j]); - } - } - } - return tl; -}; -TCproto.Message = function(text) { - var tl = [], i, p, tc = text.split(''), a, t, x, z; - for(i = 0; i < tc.length; ++i) { - if(tc[i] != ' ') { - p = i - tc.length / 2; - a = doc.createElement('A'); - a.href = '#'; - a.innerText = tc[i]; - x = 100 * sin(p / 9); - z = -100 * cos(p / 9); - t = new Tag(this, tc[i], a, [x,0,z], 2, 18, '#000', '#fff', 0, 0, 0, - 'monospace', 2, tc[i]); - t.Init(); - tl.push(t); - } - } - return tl; -}; -TCproto.AddAudio = function(e, t) { - if(audio === 'off') - return; - var au = e.getElementsByTagName('audio'); - if(au.length) { - t.SetAudio(au[0]); - this.hasAudio = 1; - } -}; -TCproto.CreateTag = function(e) { - var im, i, t, txt, ts, font, bc, boc, p = [0, 0, 0], au; - if('text' != this.imageMode) { - im = e.getElementsByTagName('img'); - if(im.length) { - i = new Image; - i.src = im[0].src; - - if(!this.imageMode) { - t = new Tag(this, "", e, p, 0, 0); - t.SetImage(i); - //t.Init(); - AddImage(i, im[0], im[1], t, this); - this.AddAudio(e, t); - return t; - } - } - } - if('image' != this.imageMode) { - ts = new TextSplitter(e); - txt = ts.Lines(); - if(!ts.Empty()) { - font = this.textFont || FixFont(GetProperty(e,'font-family')); - if(this.splitWidth) - txt = ts.SplitWidth(this.splitWidth, this.ctxt, font, this.textHeight); - - bc = this.bgColour == 'tag' ? GetProperty(e, 'background-color') : - this.bgColour; - boc = this.bgOutline == 'tag' ? GetProperty(e, 'color') : this.bgOutline; - } else { - ts = null; - } - } - if(ts || i) { - t = new Tag(this, txt, e, p, 2, this.textHeight + 2, - this.textColour || GetProperty(e,'color'), bc, this.bgRadius, - boc, this.bgOutlineThickness, font, this.padding, ts && ts.original); - if(i) { - t.SetImage(i); - AddImage(i, im[0], im[1], t, this); - } else { - t.Init(); - } - this.AddAudio(e, t); - return t; - } -}; -TCproto.UpdateTag = function(t, a) { - var colour = this.textColour || GetProperty(a, 'color'), - font = this.textFont || FixFont(GetProperty(a, 'font-family')), - bc = this.bgColour == 'tag' ? GetProperty(a, 'background-color') : - this.bgColour, boc = this.bgOutline == 'tag' ? GetProperty(a, 'color') : - this.bgOutline; - t.a = a; - t.title = a.title; - if(t.colour != colour || t.textFont != font || t.bgColour != bc || - t.bgOutline != boc) - t.SetFont(font, colour, bc, boc); -}; -TCproto.Weight = function(tl) { - var ll = tl.length, w, i, s, weights = [], valid, - wfrom = this.weightFrom ? this.weightFrom.split(/[, ]/) : [null], - wl = wfrom.length; - for(i = 0; i < ll; ++i) { - weights[i] = []; - for(s = 0; s < wl; ++s) { - w = FindWeight(tl[i].a, wfrom[s], this.textHeight); - if(!this.max_weight[s] || w > this.max_weight[s]) - this.max_weight[s] = w; - if(!this.min_weight[s] || w < this.min_weight[s]) - this.min_weight[s] = w; - weights[i][s] = w; - } - } - for(s = 0; s < wl; ++s) { - if(this.max_weight[s] > this.min_weight[s]) - valid = 1; - } - if(valid) { - for(i = 0; i < ll; ++i) { - tl[i].SetWeight(weights[i]); - } - } -}; -TCproto.Load = function() { - var tl = this.GetTags(), taglist = [], shape, t, - shapeArgs, rx, ry, rz, vl, i, tagmap = [], pfuncs = { - sphere: PointsOnSphere, - vcylinder: PointsOnCylinderV, - hcylinder: PointsOnCylinderH, - vring: PointsOnRingV, - hring: PointsOnRingH - }; - - if(tl.length) { - tagmap.length = tl.length; - for(i = 0; i < tl.length; ++i) - tagmap[i] = i; - this.shuffleTags && Shuffle(tagmap); - rx = 100 * this.radiusX; - ry = 100 * this.radiusY; - rz = 100 * this.radiusZ; - this.max_radius = max(rx, max(ry, rz)); - - for(i = 0; i < tl.length; ++i) { - t = this.CreateTag(tl[tagmap[i]]); - if(t) - taglist.push(t); - } - this.weight && this.Weight(taglist, true); - - if(this.shapeArgs) { - this.shapeArgs[0] = taglist.length; - } else { - shapeArgs = this.shape.toString().split(/[(),]/); - shape = shapeArgs.shift(); - if(typeof window[shape] === 'function') - this.shape = window[shape]; - else - this.shape = pfuncs[shape] || pfuncs.sphere; - this.shapeArgs = [taglist.length, rx, ry, rz].concat(shapeArgs); - } - vl = this.shape.apply(this, this.shapeArgs); - this.listLength = taglist.length; - for(i = 0; i < taglist.length; ++i) - taglist[i].position = new Vector(vl[i][0], vl[i][1], vl[i][2]); - } - if(this.noTagsMessage && !taglist.length) { - i = (this.imageMode && this.imageMode != 'both' ? this.imageMode + ' ': ''); - taglist = this.Message('No ' + i + 'tags'); - } - this.taglist = taglist; -}; -TCproto.Update = function() { - var tl = this.GetTags(), newlist = [], - taglist = this.taglist, found, - added = [], removed = [], vl, ol, nl, i, j; - - if(!this.shapeArgs) - return this.Load(); - - if(tl.length) { - nl = this.listLength = tl.length; - ol = taglist.length; - - // copy existing list, populate "removed" - for(i = 0; i < ol; ++i) { - newlist.push(taglist[i]); - removed.push(i); - } - - // find added and removed tags - for(i = 0; i < nl; ++i) { - for(j = 0, found = 0; j < ol; ++j) { - if(taglist[j].EqualTo(tl[i])) { - this.UpdateTag(newlist[j], tl[i]); - found = removed[j] = -1; - } - } - if(!found) - added.push(i); - } - - // clean out found tags from removed list - for(i = 0, j = 0; i < ol; ++i) { - if(removed[j] == -1) - removed.splice(j,1); - else - ++j; - } - - // insert new tags in gaps where old tags removed - if(removed.length) { - Shuffle(removed); - while(removed.length && added.length) { - i = removed.shift(); - j = added.shift(); - newlist[i] = this.CreateTag(tl[j]); - } - - // remove any more (in reverse order) - removed.sort(function(a,b) {return a-b}); - while(removed.length) { - newlist.splice(removed.pop(), 1); - } - } - - // add any extra tags - j = newlist.length / (added.length + 1); - i = 0; - while(added.length) { - newlist.splice(ceil(++i * j), 0, this.CreateTag(tl[added.shift()])); - } - - // assign correct positions to tags - this.shapeArgs[0] = nl = newlist.length; - vl = this.shape.apply(this, this.shapeArgs); - for(i = 0; i < nl; ++i) - newlist[i].position = new Vector(vl[i][0], vl[i][1], vl[i][2]); - - // reweight tags - this.weight && this.Weight(newlist); - } - this.taglist = newlist; -}; -TCproto.SetShadow = function(c) { - c.shadowBlur = this.shadowBlur; - c.shadowOffsetX = this.shadowOffset[0]; - c.shadowOffsetY = this.shadowOffset[1]; -}; -TCproto.LoadAudio = function() { - if(!audio && !SetupAudio()) - return; - this.audio = doc.createElement('audio'); - this.audio.src = this.activeAudio; - this.track = audio.createMediaElementSource(this.audio); - this.gain = audio.createGain(); - this.track.connect(this.gain); - this.gain.connect(audio.destination); - this.hasAudio = 1; - audioClick = function(e) { - audio.resume(); - doc.removeEventListener('click', audioClick); - }; - doc.addEventListener('click', audioClick); -}; -TCproto.ShowAudioIcon = function() { - var s = this.audioIconSize, cv = this.canvas, c = this.ctxt, - x = cv.width - s - 3, y = cv.height - s - 3, t = this.audioIconThickness, - c1 = '#000', c2 = '#fff', d = this.audioIconDark, muted = this.audioOff, - sus = 'suspended'; - if(!audio) - return; - if(!muted) - muted = (audio.state === sus); - - if(this.audioIcon && this.hasAudio) { - AudioIcon(muted,c,s,x,y,t+1,d ? c2 : c1); - AudioIcon(muted,c,s,x,y,t,d ? c1 : c2); - } -}; -TCproto.CheckAudioIcon = function() { - var s = this.audioIconSize, cv = this.canvas, t = this.audioIconThickness / 2, - x = cv.width - s - 3 - t, y = cv.height - s - 3 - t; - if(this.audioIcon && this.mx >= x && this.my >= y) - return true; -}; -TCproto.ToggleAudio = function() { - var on = this.audioOff || (audio && audio.state === 'suspended'); - on || this.currentAudio && this.currentAudio.StopAudio(); - this.audioOff = !on; -}; -TCproto.Draw = function(t) { - if(this.paused) - return; - var cv = this.canvas, cw = cv.width, ch = cv.height, max_sc = 0, - tdelta = (t - this.time) * TagCanvas.interval / 1000, - x = cw / 2 + this.offsetX, y = ch / 2 + this.offsetY, c = this.ctxt, - active, a, i, aindex = -1, tl = this.taglist, l = tl.length, - last = this.active && this.active.tag, cursor = '', - frontsel = this.frontSelect, centreDrawn = (this.centreFunc == Nop), fixed; - this.time = t; - if(this.frozen && this.drawn) - return this.Animate(cw,ch,tdelta); - fixed = this.AnimateFixed(); - c.setTransform(1,0,0,1,0,0); - for(i = 0; i < l; ++i) - tl[i].Calc(this.transform, this.fixedAlpha); - tl = SortList(tl, function(a,b) {return b.z-a.z}); - - if(fixed && this.fixedAnim.active) { - active = this.fixedAnim.tag.UpdateActive(c, x, y); - } else { - this.active = null; - if(this.CheckAudioIcon()) { - cursor = 'pointer'; - } else { - for(i = 0; i < l; ++i) { - a = this.mx >= 0 && this.my >= 0 && this.taglist[i].CheckActive(c, x, y); - if(a && a.sc > max_sc && (!frontsel || a.z <= 0)) { - active = a; - aindex = i; - active.tag = this.taglist[i]; - max_sc = a.sc; - } - } - this.active = active; - } - } - - this.txtOpt || (this.shadow && this.SetShadow(c)); - c.clearRect(0,0,cw,ch); - for(i = 0; i < l; ++i) { - if(!centreDrawn && tl[i].z <= 0) { - // run the centreFunc if the next tag is at the front - try { this.centreFunc(c, cw, ch, x, y); } - catch(e) { - alert(e); - // don't run it again - this.centreFunc = Nop; - } - centreDrawn = true; - } - - if(!(active && active.tag == tl[i] && active.PreDraw(c, tl[i], x, y))) - tl[i].Draw(c, x, y); - active && active.tag == tl[i] && active.PostDraw(c); - } - if(this.freezeActive && active) { - this.Freeze(); - } else { - this.UnFreeze(); - this.drawn = (l == this.listLength); - } - if(this.fixedCallback) { - this.fixedCallback(this,this.fixedCallbackTag); - this.fixedCallback = null; - } - fixed || this.Animate(cw, ch, tdelta); - if(active) { - active.LastDraw(c); - if(active.tag != last) { - this.currentAudio && this.currentAudio != active.tag && this.currentAudio.StopAudio(); - if(active.tag.PlayAudio()) - this.currentAudio = active.tag; - } - cursor = this.activeCursor; - } - cv.style.cursor = cursor; - this.Tooltip(active,this.taglist[aindex]); - this.audioIcon && this.ShowAudioIcon(); -}; -TCproto.TooltipNone = function() { }; -TCproto.TooltipNative = function(active,tag) { - if(active) - this.canvas.title = tag && tag.title ? tag.title : ''; - else - this.canvas.title = this.ctitle; -}; -TCproto.SetTTDiv = function(title, tag) { - var tc = this, s = tc.ttdiv.style; - if(title != tc.ttdiv.innerHTML) - s.display = 'none'; - tc.ttdiv.innerHTML = title; - tag && (tag.title = tc.ttdiv.innerHTML); - if(s.display == 'none' && ! tc.tttimer) { - tc.tttimer = setTimeout(function() { - var p = AbsPos(tc.canvas.id); - s.display = 'block'; - s.left = p.x + tc.mx + 'px'; - s.top = p.y + tc.my + 24 + 'px'; - tc.tttimer = null; - }, tc.tooltipDelay); - } -}; -TCproto.TooltipDiv = function(active,tag) { - if(active && tag && tag.title) { - this.SetTTDiv(tag.title, tag); - } else if(!active && this.mx != -1 && this.my != -1 && this.ctitle.length) { - this.SetTTDiv(this.ctitle); - } else { - this.ttdiv.style.display = 'none'; - } -}; -TCproto.Transform = function(tc, p, y) { - if(p || y) { - var sp = sin(p), cp = cos(p), sy = sin(y), cy = cos(y), - ym = new Matrix([cy,0,sy, 0,1,0, -sy,0,cy]), - pm = new Matrix([1,0,0, 0,cp,-sp, 0,sp,cp]); - tc.transform = tc.transform.mul(ym.mul(pm)); - } -}; -TCproto.AnimateFixed = function() { - var fa, t1, angle, m, d; - if(this.fadeIn) { - t1 = TimeNow() - this.startTime; - if(t1 >= this.fadeIn) { - this.fadeIn = 0; - this.fixedAlpha = 1; - } else { - this.fixedAlpha = t1 / this.fadeIn; - } - } - if(this.fixedAnim) { - if(!this.fixedAnim.transform) - this.fixedAnim.transform = this.transform; - fa = this.fixedAnim, t1 = TimeNow() - fa.t0, angle = fa.angle, - m, d = this.animTiming(fa.t, t1); - this.transform = fa.transform; - if(t1 >= fa.t) { - this.fixedCallbackTag = fa.tag; - this.fixedCallback = fa.cb; - this.fixedAnim = this.yaw = this.pitch = 0; - } else { - angle *= d; - } - m = Matrix.Rotation(angle, fa.axis); - this.transform = this.transform.mul(m); - return (this.fixedAnim != 0); - } - return false; -}; -TCproto.AnimatePosition = function(w, h, t) { - var tc = this, x = tc.mx, y = tc.my, s, r; - if(!tc.frozen && x >= 0 && y >= 0 && x < w && y < h) { - s = tc.maxSpeed, r = tc.reverse ? -1 : 1; - tc.lx || (tc.yaw = ((x * 2 * s / w) - s) * r * t); - tc.ly || (tc.pitch = ((y * 2 * s / h) - s) * -r * t); - tc.initial = null; - } else if(!tc.initial) { - if(tc.frozen && !tc.freezeDecel) - tc.yaw = tc.pitch = 0; - else - tc.Decel(tc); - } - this.Transform(tc, tc.pitch, tc.yaw); -}; -TCproto.AnimateDrag = function(w, h, t) { - var tc = this, rs = 100 * t * tc.maxSpeed / tc.max_radius / tc.zoom; - if(tc.dx || tc.dy) { - tc.lx || (tc.yaw = tc.dx * rs / tc.stretchX); - tc.ly || (tc.pitch = tc.dy * -rs / tc.stretchY); - tc.dx = tc.dy = 0; - tc.initial = null; - } else if(!tc.initial) { - tc.Decel(tc); - } - this.Transform(tc, tc.pitch, tc.yaw); -}; -TCproto.Freeze = function() { - if(!this.frozen) { - this.preFreeze = [this.yaw, this.pitch]; - this.frozen = 1; - this.drawn = 0; - } -}; -TCproto.UnFreeze = function() { - if(this.frozen) { - this.yaw = this.preFreeze[0]; - this.pitch = this.preFreeze[1]; - this.frozen = 0; - } -}; -TCproto.Decel = function(tc) { - var s = tc.minSpeed, ay = abs(tc.yaw), ap = abs(tc.pitch); - if(!tc.lx && ay > s) - tc.yaw = ay > tc.z0 ? tc.yaw * tc.decel : 0; - if(!tc.ly && ap > s) - tc.pitch = ap > tc.z0 ? tc.pitch * tc.decel : 0; -}; -TCproto.Zoom = function(r) { - this.z2 = this.z1 * (1/r); - this.drawn = 0; -}; -TCproto.Clicked = function(e) { - if(this.CheckAudioIcon()) { - this.ToggleAudio(); - return; - } - var a = this.active; - try { - if(a && a.tag) - if(this.clickToFront === false || this.clickToFront === null) - a.tag.Clicked(e); - else - this.TagToFront(a.tag, this.clickToFront, function() { - a.tag.Clicked(e); - }, true); - } catch(ex) { - } -}; -TCproto.Wheel = function(i) { - var z = this.zoom + this.zoomStep * (i ? 1 : -1); - this.zoom = min(this.zoomMax,max(this.zoomMin,z)); - this.Zoom(this.zoom); -}; -TCproto.BeginDrag = function(e) { - this.down = EventXY(e, this.canvas); - e.cancelBubble = true; - e.returnValue = false; - e.preventDefault && e.preventDefault(); -}; -TCproto.Drag = function(e, p) { - if(this.dragControl && this.down) { - var t2 = this.dragThreshold * this.dragThreshold, - dx = p.x - this.down.x, dy = p.y - this.down.y; - if(this.dragging || dx * dx + dy * dy > t2) { - this.dx = dx; - this.dy = dy; - this.dragging = 1; - this.down = p; - } - } - return this.dragging; -}; -TCproto.EndDrag = function() { - var res = this.dragging; - this.dragging = this.down = null; - return res; -}; -function PinchDistance(e) { - var t1 = e.targetTouches[0], t2 = e.targetTouches[1]; - return sqrt(pow(t2.pageX - t1.pageX, 2) + pow(t2.pageY - t1.pageY, 2)); -} -TCproto.BeginPinch = function(e) { - this.pinched = [PinchDistance(e), this.zoom]; - e.preventDefault && e.preventDefault(); -}; -TCproto.Pinch = function(e) { - var z, d, p = this.pinched; - if(!p) - return; - d = PinchDistance(e); - z = p[1] * d / p[0]; - this.zoom = min(this.zoomMax,max(this.zoomMin,z)); - this.Zoom(this.zoom); -}; -TCproto.EndPinch = function(e) { - this.pinched = null; -}; -TCproto.Pause = function() { this.paused = true; }; -TCproto.Resume = function() { this.paused = false; }; -TCproto.SetSpeed = function(i) { - this.initial = i; - this.yaw = i[0] * this.maxSpeed; - this.pitch = i[1] * this.maxSpeed; -}; -TCproto.FindTag = function(t) { - if(!Defined(t)) - return null; - Defined(t.index) && (t = t.index); - if(!IsObject(t)) - return this.taglist[t]; - var srch, tgt, i; - if(Defined(t.id)) - srch = 'id', tgt = t.id; - else if(Defined(t.text)) - srch = 'innerText', tgt = t.text; - - for(i = 0; i < this.taglist.length; ++i) - if(this.taglist[i].a[srch] == tgt) - return this.taglist[i]; -}; -TCproto.RotateTag = function(tag, lt, lg, time, callback, active) { - var t = tag.Calc(this.transform, 1), v1 = new Vector(t.x, t.y, t.z), - v2 = MakeVector(lg, lt), angle = v1.angle(v2), u = v1.cross(v2).unit(); - if(angle == 0) { - this.fixedCallbackTag = tag; - this.fixedCallback = callback; - } else { - this.fixedAnim = { - angle: -angle, - axis: u, - t: time, - t0: TimeNow(), - cb: callback, - tag: tag, - active: active - }; - } -}; -TCproto.TagToFront = function(tag, time, callback, active) { - this.RotateTag(tag, 0, 0, time, callback, active); -}; -TCproto.Volume = function(vol) { - this.audioVolume = vol * 1; -}; -TagCanvas.Start = function(id,l,o) { - TagCanvas.Delete(id); - TagCanvas.tc[id] = new TagCanvas(id,l,o); -}; -function tccall(f,id) { - TagCanvas.tc[id] && TagCanvas.tc[id][f](); -} -TagCanvas.Linear = function(t, t0) { return t0 / t; } -TagCanvas.Smooth = function(t, t0) { return 0.5 - cos(t0 * Math.PI / t) / 2; } -TagCanvas.Pause = function(id) { tccall('Pause',id); }; -TagCanvas.Resume = function(id) { tccall('Resume',id); }; -TagCanvas.Reload = function(id) { tccall('Load',id); }; -TagCanvas.Update = function(id) { tccall('Update',id); }; -TagCanvas.SetSpeed = function(id, speed) { - if(IsObject(speed) && TagCanvas.tc[id] && - !isNaN(speed[0]) && !isNaN(speed[1])) { - TagCanvas.tc[id].SetSpeed(speed); - return true; - } - return false; -}; -TagCanvas.TagToFront = function(id, options) { - if(!IsObject(options)) - return false; - options.lat = options.lng = 0; - return TagCanvas.RotateTag(id, options); -}; -TagCanvas.RotateTag = function(id, options) { - if(IsObject(options) && TagCanvas.tc[id]) { - if(isNaN(options.time)) - options.time = 500; - var tt = TagCanvas.tc[id].FindTag(options); - if(tt) { - TagCanvas.tc[id].RotateTag(tt, options.lat, options.lng, - options.time, options.callback, options.active); - return true; - } - } - return false; -}; -TagCanvas.Delete = function(id) { - var i, c; - if(handlers[id]) { - c = doc.getElementById(id); - if(c) { - for(i = 0; i < handlers[id].length; ++i) - RemoveHandler(handlers[id][i][0], handlers[id][i][1], c); - } - } - delete handlers[id]; - delete TagCanvas.tc[id]; -}; -TagCanvas.tc = {}; -TagCanvas.options = { -z1: 20000, -z2: 20000, -z0: 0.0002, -freezeActive: false, -freezeDecel: false, -activeCursor: 'pointer', -pulsateTo: 1, -pulsateTime: 3, -reverse: false, -depth: 0.5, -maxSpeed: 0.05, -minSpeed: 0, -decel: 0.95, -interval: 20, -minBrightness: 0.1, -maxBrightness: 1, -outlineColour: '#ffff99', -outlineThickness: 2, -outlineOffset: 5, -outlineMethod: 'outline', -outlineRadius: 0, -textColour: '#ff99ff', -textHeight: 15, -textFont: 'Helvetica, Arial, sans-serif', -shadow: '#000', -shadowBlur: 0, -shadowOffset: [0,0], -initial: null, -hideTags: true, -zoom: 1, -weight: false, -weightMode: 'size', -weightFrom: null, -weightSize: 1, -weightSizeMin: null, -weightSizeMax: null, -weightGradient: {0:'#f00', 0.33:'#ff0', 0.66:'#0f0', 1:'#00f'}, -txtOpt: true, -txtScale: 2, -frontSelect: false, -wheelZoom: true, -zoomMin: 0.3, -zoomMax: 3, -zoomStep: 0.05, -shape: 'sphere', -lock: null, -tooltip: null, -tooltipDelay: 300, -tooltipClass: 'tctooltip', -radiusX: 1, -radiusY: 1, -radiusZ: 1, -stretchX: 1, -stretchY: 1, -offsetX: 0, -offsetY: 0, -shuffleTags: false, -noSelect: false, -noMouse: false, -imageScale: 1, -paused: false, -dragControl: false, -dragThreshold: 4, -centreFunc: Nop, -splitWidth: 0, -animTiming: 'Smooth', -clickToFront: false, -fadeIn: 0, -padding: 0, -bgColour: null, -bgRadius: 0, -bgOutline: null, -bgOutlineThickness: 0, -outlineIncrease: 4, -textAlign: 'centre', -textVAlign: 'middle', -imageMode: null, -imagePosition: null, -imagePadding: 2, -imageAlign: 'centre', -imageVAlign: 'middle', -noTagsMessage: true, -centreImage: null, -pinchZoom: false, -repeatTags: 0, -minTags: 0, -imageRadius: 0, -scrollPause: false, -outlineDash: 0, -outlineDashSpace: 0, -outlineDashSpeed: 1, -activeAudio: '', -audioVolume: 1, -audioIcon: 1, -audioIconSize: 20, -audioIconThickness: 2, -audioIconDark: 0, -altImage: 0 -}; -for(i in TagCanvas.options) TagCanvas[i] = TagCanvas.options[i]; -window.TagCanvas = TagCanvas; -jQuery.fn.tagcanvas = function(options, lctr) { - var fn = { - pause: function() { - $(this).each(function() { tccall('Pause',$(this)[0].id); }); - }, - resume: function() { - $(this).each(function() { tccall('Resume',$(this)[0].id); }); - }, - reload: function() { - $(this).each(function() { tccall('Load',$(this)[0].id); }); - }, - update: function() { - $(this).each(function() { tccall('Update',$(this)[0].id); }); - }, - tagtofront: function() { - $(this).each(function() { TagCanvas.TagToFront($(this)[0].id, lctr); }); - }, - rotatetag: function() { - $(this).each(function() { TagCanvas.RotateTag($(this)[0].id, lctr); }); - }, - 'delete': function() { - $(this).each(function() { TagCanvas.Delete($(this)[0].id); }); - }, - setspeed: function() { - $(this).each(function() { TagCanvas.SetSpeed($(this)[0].id, lctr); }); - } - }; - if(typeof options == 'string' && fn[options]) { - fn[options].apply(this); - return this; - } else { - TagCanvas.jquery = 1; - $(this).each(function() { TagCanvas.Start($(this)[0].id, lctr, options); }); - return TagCanvas.started; - } -}; - -// set a flag for when the window has loaded -AddHandler('load',function(){TagCanvas.loaded=1},window); -})(jQuery); diff --git a/jquery.tagcanvas.min.js b/jquery.tagcanvas.min.js deleted file mode 100644 index a4b0b3e..0000000 --- a/jquery.tagcanvas.min.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (C) 2010-2021 Graham Breach - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -/** - * jQuery.tagcanvas 2.11 - * For more information, please contact - */ -(function(f){"use strict";var r,F,p=Math.abs,s=Math.sin,m=Math.cos,h=Math.max,i=Math.min,W=Math.ceil,G=Math.sqrt,x=Math.pow,K={},D={},Q={0:"0,",1:"17,",2:"34,",3:"51,",4:"68,",5:"85,",6:"102,",7:"119,",8:"136,",9:"153,",a:"170,",A:"170,",b:"187,",B:"187,",c:"204,",C:"204,",d:"221,",D:"221,",e:"238,",E:"238,",f:"255,",F:"255,"},g,d,b,P,B,L,J,c=document,v,e,T,k={};for(r=0;r<256;++r)F=r.toString(16),r<16&&(F='0'+F),D[F]=D[F.toUpperCase()]=r.toString()+',';function o(a){return typeof a!='undefined'}function C(a){return typeof a=='object'&&a!=null}function I(a,c,b){return isNaN(a)?b:i(b,h(c,a))}function y(){return!1}function q(){return(new Date).valueOf()}function aI(c,d){var b=[],e=c.length,a;for(a=0;a=1)?0:a<=-1?Math.PI:Math.acos(a)},B.unit=function(){var a=this.length();return new t(this.x/a,this.y/a,this.z/a)};function aH(b,a){a=a*Math.PI/180,b=b*Math.PI/180;var c=s(b)*m(a),d=-s(a),e=-m(b)*m(a);return new t(c,d,e)}function n(a){this[1]={1:a[0],2:a[1],3:a[2]},this[2]={1:a[3],2:a[4],3:a[5]},this[3]={1:a[6],2:a[7],3:a[8]}}P=n.prototype,n.Identity=function(){return new n([1,0,0,0,1,0,0,0,1])},n.Rotation=function(e,a){var c=s(e),d=m(e),b=1-d;return new n([d+x(a.x,2)*b,a.x*a.y*b-a.z*c,a.x*a.z*b+a.y*c,a.y*a.x*b+a.z*c,d+x(a.y,2)*b,a.y*a.z*b-a.x*c,a.z*a.x*b-a.y*c,a.z*a.y*b+a.x*c,d+x(a.z,2)*b])},P.mul=function(c){var d=[],a,b,e=c.xform?1:0;for(a=1;a<=3;++a)for(b=1;b<=3;++b)e?d.push(this[a][1]*c[1][b]+this[a][2]*c[2][b]+this[a][3]*c[3][b]):d.push(this[a][b]*c);return new n(d)},P.xform=function(b){var a={},c=b.x,d=b.y,e=b.z;return a.x=c*this[1][1]+d*this[2][1]+e*this[3][1],a.y=c*this[1][2]+d*this[2][2]+e*this[3][2],a.z=c*this[1][3]+d*this[2][3]+e*this[3][3],a};function aG(g,j,k,l,f){var a,b,c,d,e=[],h=2/g,i;i=Math.PI*(3-G(5)+(parseFloat(f)?parseFloat(f):0));for(a=0;a0)}function au(a,c,f,d){var e=a.createLinearGradient(0,0,c,0),b;for(b in d)e.addColorStop(1-b,d[b]);a.fillStyle=e,a.fillRect(0,f,c,1)}function H(a,m,j){var k=1024,d=1,e=a.weightGradient,g,f,b,c;if(a.gCanvas)f=a.gCanvas.getContext('2d'),d=a.gCanvas.height;else{if(C(e[0])?d=e.length:e=[e],a.gCanvas=g=l(k,d),!g)return null;f=g.getContext('2d');for(b=0;b0?b=g*b/100:b=b*j,a=e.getContext('2d'),a.globalCompositeOperation='source-over',a.fillStyle='#fff',b>=g/2?(b=i(c,d)/2,a.beginPath(),a.moveTo(c/2,d/2),a.arc(c/2,d/2,b,0,2*Math.PI,!1),a.fill(),a.closePath()):(b=i(c/2,d/2,b),z(a,0,0,c,d,b,!0),a.fill()),a.globalCompositeOperation='source-in',a.drawImage(k,0,0,c,d),e)}function aJ(q,m,i,b,h,a,c){var g=p(c[0]),f=p(c[1]),j=m+(g>a?g+a:a*2)*b,k=i+(f>a?f+a:a*2)*b,n=b*((a||0)+(c[0]<0?g:0)),o=b*((a||0)+(c[1]<0?f:0)),e,d;return e=l(j,k),!e?null:(d=e.getContext('2d'),h&&(d.shadowColor=h),a&&(d.shadowBlur=a*b),c&&(d.shadowOffsetX=c[0]*b,d.shadowOffsetY=c[1]*b),d.drawImage(q,n,o,m,i),{image:e,width:j/b,height:k/b})}function af(m,o,k){var c=parseInt(m.toString().length*k),h=parseInt(k*2*m.length),j=l(c,h),g,i,e,f,b,d,n,a;if(!j)return null;g=j.getContext('2d'),g.fillStyle='#000',g.fillRect(0,0,c,h),Z(g,k+'px '+o,'#fff',m,0,0,0,0,[],'centre'),i=g.getImageData(0,0,c,h),e=i.width,f=i.height,a={min:{x:e,y:f},max:{x:-1,y:-1}};for(d=0;d0&&(ba.max.x&&(a.max.x=b),da.max.y&&(a.max.y=d));return e!=c&&(a.min.x*=c/e,a.max.x*=c/e),f!=h&&(a.min.y*=c/f,a.max.y*=c/f),j=null,a}function S(a){return"'"+a.replace(/(\'|\")/g,'').replace(/\s*,\s*/g,"', '")+"'"}function w(b,d,a){a=a||c,a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent('on'+b,d)}function ak(b,d,a){a=a||c,a.removeEventListener?a.removeEventListener(b,d):a.detachEvent('on'+b,d)}function E(g,e,i,a,b){var l=b.imageScale,h,c,k,m,f,d;if(!e.complete)return w('load',function(){E(g,e,i,a,b)},e);if(!g.complete)return w('load',function(){E(g,e,i,a,b)},g);if(i&&!i.complete)return w('load',function(){E(g,e,i,a,b)},i);e.width=e.width,e.height=e.height,l&&(g.width=e.width*l,g.height=e.height*l),a.iw=g.width,a.ih=g.height,b.txtOpt&&(c=g,h=b.zoomMax*b.txtScale,f=a.iw*h,d=a.ih*h,f0?(a.iw+=2*b.outlineIncrease,a.ih+=2*b.outlineIncrease,f=h*a.iw,d=h*a.ih,c=U(a.fimage,f,d),a.oimage=c,a.fimage=O(a.fimage,a.oimage.width,a.oimage.height)):(f=h*(a.iw+2*b.outlineIncrease),d=h*(a.ih+2*b.outlineIncrease),c=U(a.fimage,f,d),a.oimage=O(c,a.fimage.width,a.fimage.height))))),a.alt=i,a.Init()}function j(a,d){var b=c.defaultView,e=d.replace(/\-([a-z])/g,function(a){return a.charAt(1).toUpperCase()});return b&&b.getComputedStyle&&b.getComputedStyle(a,null).getPropertyValue(d)||a.currentStyle&&a.currentStyle[e]}function al(c,d,e){var b=1,a;return d?b=1*(c.getAttribute(d)||e):(a=j(c,'font-size'))&&(b=a.indexOf('px')>-1&&a.replace('px','')*1||a.indexOf('pt')>-1&&a.replace('pt','')*1.25||a*3.3),b}function A(a){return a.target&&o(a.target.id)?a.target.id:a.srcElement.parentNode.id}function N(a,c){var b,d,e=parseInt(j(c,'width'))/c.width,f=parseInt(j(c,'height'))/c.height;return o(a.offsetX)?b={x:a.offsetX,y:a.offsetY}:(d=Y(c.id),o(a.changedTouches)&&(a=a.changedTouches[0]),a.pageX&&(b={x:a.pageX-d.x,y:a.pageY-d.y})),b&&e&&f&&(b.x/=e,b.y/=f),b}function ao(c){var d=c.target||c.fromElement.parentNode,b=a.tc[d.id];b&&(b.mx=b.my=-1,b.UnFreeze(),b.EndDrag())}function ab(e){var g,c=a,b,d,f=A(e);for(g in c.tc)b=c.tc[g],b.tttimer&&(clearTimeout(b.tttimer),b.tttimer=null);f&&c.tc[f]&&(b=c.tc[f],(d=N(e,b.canvas))&&(b.mx=d.x,b.my=d.y,b.Drag(e,d)),b.drawn=0)}function aq(b){var e=a,f=c.addEventListener?0:1,d=A(b);d&&b.button==f&&e.tc[d]&&e.tc[d].BeginDrag(b)}function ar(b){var f=a,g=c.addEventListener?0:1,e=A(b),d;e&&b.button==g&&f.tc[e]&&(d=f.tc[e],ab(b),!d.EndDrag()&&!d.touchState&&d.Clicked(b))}function at(c){var e=A(c),b=e&&a.tc[e],d;b&&c.changedTouches&&(c.touches.length==1&&b.touchState==0?(b.touchState=1,b.BeginDrag(c),(d=N(c,b.canvas))&&(b.mx=d.x,b.my=d.y,b.drawn=0)):c.targetTouches.length==2&&b.pinchZoom?(b.touchState=3,b.EndDrag(),b.BeginPinch(c)):(b.EndDrag(),b.EndPinch(),b.touchState=0))}function X(c){var d=A(c),b=d&&a.tc[d];if(b&&c.changedTouches){switch(b.touchState){case 1:b.Draw(),b.Clicked();break;break;case 2:b.EndDrag();break;case 3:b.EndPinch()}b.touchState=0}}function av(c){var f,e=a,b,d,g=A(c);for(f in e.tc)b=e.tc[f],b.tttimer&&(clearTimeout(b.tttimer),b.tttimer=null);if(b=g&&e.tc[g],b&&c.changedTouches&&b.touchState){switch(b.touchState){case 1:case 2:(d=N(c,b.canvas))&&(b.mx=d.x,b.my=d.y,b.Drag(c,d)&&(b.touchState=2));break;case 3:b.Pinch(c)}b.drawn=0}}function V(b){var d=a,c=A(b);c&&d.tc[c]&&(b.cancelBubble=!0,b.returnValue=!1,b.preventDefault&&b.preventDefault(),d.tc[c].Wheel((b.wheelDelta||b.detail)>0))}function ax(d){var c,b=a;clearTimeout(b.scrollTimer);for(c in b.tc)b.tc[c].Pause();b.scrollTimer=setTimeout(function(){var b,c=a;for(b in c.tc)c.tc[b].Resume()},b.scrollPause)}function am(){ai(q())}function ai(b){var c=a.tc,d;a.NextFrame(a.interval),b=b||q();for(d in c)c[d].Draw(b)}function aA(){requestAnimationFrame(ai)}function aB(a){setTimeout(am,a)}function Y(f){var g=c.getElementById(f),b=g.getBoundingClientRect(),a=c.documentElement,d=c.body,e=window,h=e.pageXOffset||a.scrollLeft,i=e.pageYOffset||a.scrollTop,j=a.clientLeft||d.clientLeft,k=a.clientTop||d.clientTop;return{x:b.left+h-j,y:b.top+i-k}}function aD(a,b,d,e){var c=a.radius*a.z1/(a.z1+a.z2+b.z);return{x:b.x*c*d,y:b.y*c*e,z:b.z,w:(a.z1-b.z)/a.z2}}function _(a){this.e=a,this.br=0,this.line=[],this.text=[],this.original=a.innerText||a.textContent}L=_.prototype,L.Empty=function(){for(var a=0;ah?(d.push(this.line.join(' ')),this.line=[a[b]]):this.line.push(a[b]);d.push(this.line.join(' '))}return this.text=d};function $(a,b){this.ts=null,this.tc=a,this.tag=b,this.x=this.y=this.w=this.h=this.sc=1,this.z=0,this.pulse=1,this.pulsate=a.pulsateTo<1,this.colour=a.outlineColour,this.adash=~~a.outlineDash,this.agap=~~a.outlineDashSpace||this.adash,this.aspeed=a.outlineDashSpeed*1,this.colour=='tag'?this.colour=j(b.a,'color'):this.colour=='tagbg'&&(this.colour=j(b.a,'background-color')),this.Draw=this.pulsate?this.DrawPulsate:this.DrawSimple,this.radius=a.outlineRadius|0,this.SetMethod(a.outlineMethod,a.altImage)}g=$.prototype,g.SetMethod=function(a,d){var b={block:['PreDraw','DrawBlock'],colour:['PreDraw','DrawColour'],outline:['PostDraw','DrawOutline'],classic:['LastDraw','DrawOutline'],size:['PreDraw','DrawSize'],none:['LastDraw']},c=b[a]||b.outline;a=='none'?this.Draw=function(){return 1}:this.drawFunc=this[c[1]],this[c[0]]=this.Draw,d&&(this.RealPreDraw=this.PreDraw,this.PreDraw=this.DrawAlt)},g.Update=function(d,e,i,j,a,f,g,h){var b=this.tc.outlineOffset,c=2*b;this.x=a*d+g-b,this.y=a*e+h-b,this.w=a*i+c,this.h=a*j+c,this.sc=a,this.z=f},g.Ants=function(k){if(!this.adash)return;var b=this.adash,c=this.agap,a=this.aspeed,j=b+c,h=0,g=b,f=c,i=0,d=0,e;a&&(d=p(a)*(q()-this.ts)/50,a<0&&(d=864e4-d),a=~~d%j),a?(b>=a?(h=b-a,g=a):(f=j-a,i=c-f),e=[h,f,g,i]):e=[b,c],k.setLineDash(e)},g.DrawOutline=function(a,d,e,b,c,f){var g=i(this.radius,c/2,b/2);a.strokeStyle=f,this.Ants(a),z(a,d,e,b,c,g,!0)},g.DrawSize=function(i,n,m,l,k,j,a,h,g){var f=a.w,e=a.h,c,b,d;return this.pulsate?(a.image?d=(a.image.height+this.tc.outlineIncrease)/a.image.height:d=a.oscale,b=a.fimage||a.image,c=1+(d-1)*(1-this.pulse),a.h*=c,a.w*=c):b=a.oimage,a.alpha=1,a.Draw(i,h,g,b),a.h=e,a.w=f,1},g.DrawColour=function(d,h,i,e,f,g,a,b,c){return a.oimage?(this.pulse<1?(a.alpha=1-x(this.pulse,2),a.Draw(d,b,c,a.fimage),a.alpha=this.pulse):a.alpha=1,a.Draw(d,b,c,a.oimage),1):this[a.image?'DrawColourImage':'DrawColourText'](d,h,i,e,f,g,a,b,c)},g.DrawColourText=function(f,h,i,j,g,e,a,b,c){var d=a.colour;return a.colour=e,a.alpha=1,a.Draw(f,b,c),a.colour=d,1},g.DrawColourImage=function(a,q,p,o,n,m,g,r,k){var f=a.canvas,e=~~h(q,0),d=~~h(p,0),c=i(f.width-e,o)+.5|0,b=i(f.height-d,n)+.5|0,j;return v?(v.width=c,v.height=b):v=l(c,b),!v?this.SetMethod('outline'):(j=v.getContext('2d'),j.drawImage(f,e,d,c,b,0,0,c,b),a.clearRect(e,d,c,b),this.pulsate?g.alpha=1-x(this.pulse,2):g.alpha=1,g.Draw(a,r,k),a.setTransform(1,0,0,1,0,0),a.save(),a.beginPath(),a.rect(e,d,c,b),a.clip(),a.globalCompositeOperation='source-in',a.fillStyle=m,a.fillRect(e,d,c,b),a.restore(),a.globalAlpha=1,a.globalCompositeOperation='destination-over',a.drawImage(v,0,0,c,b,e,d,c,b),a.globalCompositeOperation='source-over',1)},g.DrawAlt=function(b,a,c,d,f,g){var e=this.RealPreDraw(b,a,c,d,f,g);return a.alt&&(a.DrawImage(b,c,d,a.alt),e=1),e},g.DrawBlock=function(a,d,e,b,c,f){var g=i(this.radius,c/2,b/2);a.fillStyle=f,z(a,d,e,b,c,g)},g.DrawSimple=function(a,b,c,d,e,f){var g=this.tc;return a.setTransform(1,0,0,1,0,0),a.strokeStyle=this.colour,a.lineWidth=g.outlineThickness,a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0,a.globalAlpha=f?e:1,this.drawFunc(a,this.x,this.y,this.w,this.h,this.colour,b,c,d)},g.DrawPulsate=function(h,d,e,f){var g=q()-this.ts,c=this.tc,b=c.pulsateTo+(1-c.pulsateTo)*(.5+m(2*Math.PI*g/(1e3*c.pulsateTime))/2);return this.pulse=b=a.Smooth(1,b),this.DrawSimple(h,d,e,f,b,1)},g.Active=function(d,a,b){var c=a>=this.x&&b>=this.y&&a<=this.x+this.w&&b<=this.y+this.h;return c?this.ts=this.ts||q():this.ts=null,c},g.PreDraw=g.PostDraw=g.LastDraw=y;function M(a,h,c,b,e,f,g,d,i,j,k,l,m,n){this.tc=a,this.image=null,this.text=h,this.text_original=n,this.line_widths=[],this.title=c.title||null,this.a=c,this.position=new t(b[0],b[1],b[2]),this.x=this.y=this.z=0,this.w=e,this.h=f,this.colour=g||a.textColour,this.bgColour=d||a.bgColour,this.bgRadius=i|0,this.bgOutline=j||this.colour,this.bgOutlineThickness=k|0,this.textFont=l||a.textFont,this.padding=m|0,this.sc=this.alpha=1,this.weighted=!a.weight,this.outline=new $(a,this),this.audio=null}d=M.prototype,d.Init=function(b){var a=this.tc;this.textHeight=a.textHeight,this.HasText()?this.Measure(a.ctxt,a):(this.w=this.iw,this.h=this.ih),this.SetShadowColour=a.shadowAlpha?this.SetShadowColourAlpha:this.SetShadowColourFixed,this.SetDraw(a)},d.Draw=y,d.HasText=function(){return this.text&&this.text[0].length>0},d.EqualTo=function(a){var b=a.getElementsByTagName('img');return this.a.href!=a.href?0:b.length?this.image.src==b[0].src:(a.innerText||a.textContent)==this.text_original},d.SetImage=function(a){this.image=this.fimage=a},d.SetAudio=function(a){this.audio=a,this.audio.load()},d.SetDraw=function(a){this.Draw=this.fimage?a.ie>7?this.DrawImageIE:this.DrawImage:this.DrawText,a.noSelect&&(this.CheckActive=y)},d.MeasureText=function(d){var a,e=this.text.length,b=0,c;for(a=0;a0?c=O(c,this.oimage.width,this.oimage.height):this.oimage=O(this.oimage,c.width,c.height)),c&&(this.fimage=c,l=this.fimage.width/b,j=this.fimage.height/b),this.SetDraw(a),a.txtOpt=!!this.fimage),this.h=j,this.w=l},d.SetFont=function(a,b,c,d){this.textFont=a,this.colour=b,this.bgColour=c,this.bgOutline=d,this.Measure(this.tc.ctxt,this.tc)},d.SetWeight=function(c){var b=this.tc,e=b.weightMode.split(/[, ]/),d,a,f=c.length;if(!this.HasText())return;this.weighted=!0;for(a=0;a0&&a.weightSizeMax>a.weightSizeMin?this.textHeight=a.weightSize*(a.weightSizeMin+(a.weightSizeMax-a.weightSizeMin)*c):this.textHeight=h(1,b*a.weightSize))},d.SetShadowColourFixed=function(a,b,c){a.shadowColor=b},d.SetShadowColourAlpha=function(a,b,c){a.shadowColor=ay(b,c)},d.DrawText=function(a,h,i){var e=this.tc,g=this.x,f=this.y,c=this.sc,b,d;a.globalAlpha=this.alpha,a.fillStyle=this.colour,e.shadow&&this.SetShadowColour(a,e.shadow,this.alpha),a.font=this.font,g+=h/c,f+=i/c-this.h/2;for(b=0;b{this.stopped?this.audio.pause():this.playing=1}),1}};function a(f,m,j){var d,g,b=c.getElementById(f),l=['id','class','innerHTML'];if(!b)throw 0;if(o(window.G_vmlCanvasManager)&&(b=window.G_vmlCanvasManager.initElement(b),this.ie=parseFloat(navigator.appVersion.split('MSIE')[1])),b&&(!b.getContext||!b.getContext('2d').fillText)){g=c.createElement('DIV');for(d=0;d0?a.scrollPause=~~this.scrollPause:this.scrollPause=0,this.minTags>0&&this.repeatTags<1&&(d=this.GetTags().length)&&(this.repeatTags=W(this.minTags/d)-1),this.transform=n.Identity(),this.startTime=this.time=q(),this.mx=this.my=-1,this.centreImage&&az(this),this.Animate=this.dragControl?this.AnimateDrag:this.AnimatePosition,this.animTiming=typeof a[this.animTiming]=='function'?a[this.animTiming]:a.Smooth,this.shadowBlur||this.shadowOffset[0]||this.shadowOffset[1]?(this.ctxt.shadowColor=this.shadow,this.shadow=this.ctxt.shadowColor,this.shadowAlpha=aw()):delete this.shadow,this.activeAudio===!1?e='off':this.activeAudio&&this.LoadAudio(),this.Load(),m&&this.hideTags&&function(b){a.loaded?b.HideTags():w('load',function(){b.HideTags()},window)}(this),this.yaw=this.initial?this.initial[0]*this.maxSpeed:0,this.pitch=this.initial?this.initial[1]*this.maxSpeed:0,this.tooltip?(this.ctitle=b.title,b.title='',this.tooltip=='native'?this.Tooltip=this.TooltipNative:(this.Tooltip=this.TooltipDiv,this.ttdiv||(this.ttdiv=c.createElement('div'),this.ttdiv.className=this.tooltipClass,this.ttdiv.style.position='absolute',this.ttdiv.style.zIndex=b.style.zIndex+1,w('mouseover',function(a){a.target.style.display='none'},this.ttdiv),c.body.appendChild(this.ttdiv)))):this.Tooltip=this.TooltipNone,!this.noMouse&&!k[f]){k[f]=[['mousemove',ab],['mouseout',ao],['mouseup',ar],['touchstart',at],['touchend',X],['touchcancel',X],['touchmove',av]],this.dragControl&&(k[f].push(['mousedown',aq]),k[f].push(['selectstart',y])),this.wheelZoom&&(k[f].push(['mousewheel',V]),k[f].push(['DOMMouseScroll',V])),this.scrollPause&&k[f].push(['scroll',ax,window]);for(d=0;dthis.max_weight[a])&&(this.max_weight[a]=c),(!this.min_weight[a]||cthis.min_weight[a]&&(g=1);if(g)for(b=0;b=d&&this.my>=e)return!0},b.ToggleAudio=function(){var a=this.audioOff||e&&e.state==='suspended';a||this.currentAudio&&this.currentAudio.StopAudio(),this.audioOff=!a},b.Draw=function(s){if(this.paused)return;var l=this.canvas,i=l.width,j=l.height,q=0,p=(s-this.time)*a.interval/1e3,h=i/2+this.offsetX,g=j/2+this.offsetY,d=this.ctxt,b,f,c,o=-1,e=this.taglist,k=e.length,t=this.active&&this.active.tag,m='',u=this.frontSelect,r=this.centreFunc==y,n;if(this.time=s,this.frozen&&this.drawn)return this.Animate(i,j,p);n=this.AnimateFixed(),d.setTransform(1,0,0,1,0,0);for(c=0;c=0&&this.my>=0&&this.taglist[c].CheckActive(d,h,g),f&&f.sc>q&&(!u||f.z<=0)&&(b=f,o=c,b.tag=this.taglist[c],q=f.sc);this.active=b}this.txtOpt||this.shadow&&this.SetShadow(d),d.clearRect(0,0,i,j);for(c=0;c=this.fadeIn?(this.fadeIn=0,this.fixedAlpha=1):this.fixedAlpha=b/this.fadeIn),this.fixedAnim)&&(this.fixedAnim.transform||(this.fixedAnim.transform=this.transform),a=this.fixedAnim,b=q()-a.t0,c=a.angle,d,e=this.animTiming(a.t,b),this.transform=a.transform,b>=a.t?(this.fixedCallbackTag=a.tag,this.fixedCallback=a.cb,this.fixedAnim=this.yaw=this.pitch=0):c*=e,d=n.Rotation(c,a.axis),this.transform=this.transform.mul(d),this.fixedAnim!=0)},b.AnimatePosition=function(g,h,f){var a=this,d=a.mx,e=a.my,b,c;!a.frozen&&d>=0&&e>=0&&db&&(a.yaw=c>a.z0?a.yaw*a.decel:0),!a.ly&&d>b&&(a.pitch=d>a.z0?a.pitch*a.decel:0)},b.Zoom=function(a){this.z2=this.z1*(1/a),this.drawn=0},b.Clicked=function(b){if(this.CheckAudioIcon()){this.ToggleAudio();return}var a=this.active;try{a&&a.tag&&(this.clickToFront===!1||this.clickToFront===null?a.tag.Clicked(b):this.TagToFront(a.tag,this.clickToFront,function(){a.tag.Clicked(b)},!0))}catch(a){}},b.Wheel=function(a){var b=this.zoom+this.zoomStep*(a?1:-1);this.zoom=i(this.zoomMax,h(this.zoomMin,b)),this.Zoom(this.zoom)},b.BeginDrag=function(a){this.down=N(a,this.canvas),a.cancelBubble=!0,a.returnValue=!1,a.preventDefault&&a.preventDefault()},b.Drag=function(e,a){if(this.dragControl&&this.down){var d=this.dragThreshold*this.dragThreshold,b=a.x-this.down.x,c=a.y-this.down.y;(this.dragging||b*b+c*c>d)&&(this.dx=b,this.dy=c,this.dragging=1,this.down=a)}return this.dragging},b.EndDrag=function(){var a=this.dragging;return this.dragging=this.down=null,a};function ag(a){var b=a.targetTouches[0],c=a.targetTouches[1];return G(x(c.pageX-b.pageX,2)+x(c.pageY-b.pageY,2))}b.BeginPinch=function(a){this.pinched=[ag(a),this.zoom],a.preventDefault&&a.preventDefault()},b.Pinch=function(d){var b,c,a=this.pinched;if(!a)return;c=ag(d),b=a[1]*c/a[0],this.zoom=i(this.zoomMax,h(this.zoomMin,b)),this.Zoom(this.zoom)},b.EndPinch=function(a){this.pinched=null},b.Pause=function(){this.paused=!0},b.Resume=function(){this.paused=!1},b.SetSpeed=function(a){this.initial=a,this.yaw=a[0]*this.maxSpeed,this.pitch=a[1]*this.maxSpeed},b.FindTag=function(a){if(!o(a))return null;if(o(a.index)&&(a=a.index),!C(a))return this.taglist[a];var c,d,b;o(a.id)?(c='id',d=a.id):o(a.text)&&(c='innerText',d=a.text);for(b=0;b", + "Misskey Project" + ], + "license": "LGPL-3.0-only", + "packageManager": "pnpm@10.14.0", + "devDependencies": { + "tsc-alias": "1.8.16", + "typescript": "5.9.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..06b3f86 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,320 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + tsc-alias: + specifier: 1.8.16 + version: 1.8.16 + typescript: + specifier: 5.9.3 + version: 5.9.3 + +packages: + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + commander@9.5.0: + resolution: {integrity: sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==} + engines: {node: ^12.20.0 || >=14} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + get-tsconfig@4.13.0: + resolution: {integrity: sha512-1VKTZJCwBrvbd+Wn3AOgQP/2Av+TfTCOlE4AcRJE72W1ksZXbAx8PPBR9RzgTeSPzlPMHrbANMH3LbltH73wxQ==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mylas@2.1.14: + resolution: {integrity: sha512-BzQguy9W9NJgoVn2mRWzbFrFWWztGCcng2QI9+41frfk+Athwgx3qhqhvStz7ExeUUu7Kzw427sNzHpEZNINog==} + engines: {node: '>=16.0.0'} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + plimit-lit@1.6.1: + resolution: {integrity: sha512-B7+VDyb8Tl6oMJT9oSO2CW8XC/T4UcJGrwOVoNGwOQsQYhlpfajmrMj5xeejqaASq3V/EqThyOeATEOMuSEXiA==} + engines: {node: '>=12'} + + queue-lit@1.5.2: + resolution: {integrity: sha512-tLc36IOPeMAubu8BkW8YDBV+WyIgKlYU7zUNs0J5Vk9skSZ4JfGlPOqplP0aHdfv7HL0B2Pg6nwiq60Qc6M2Hw==} + engines: {node: '>=12'} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + tsc-alias@1.8.16: + resolution: {integrity: sha512-QjCyu55NFyRSBAl6+MTFwplpFcnm2Pq01rR/uxfqJoLMm6X3O14KEGtaSDZpJYaE1bJBGDjD0eSuiIWPe2T58g==} + engines: {node: '>=16.20.2'} + hasBin: true + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + +snapshots: + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + array-union@2.1.0: {} + + binary-extensions@2.3.0: {} + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + commander@9.5.0: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + fsevents@2.3.3: + optional: true + + get-tsconfig@4.13.0: + dependencies: + resolve-pkg-maps: 1.0.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + ignore@5.3.2: {} + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-extglob@2.1.1: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-number@7.0.0: {} + + merge2@1.4.1: {} + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mylas@2.1.14: {} + + normalize-path@3.0.0: {} + + path-type@4.0.0: {} + + picomatch@2.3.1: {} + + plimit-lit@1.6.1: + dependencies: + queue-lit: 1.5.2 + + queue-lit@1.5.2: {} + + queue-microtask@1.2.3: {} + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + resolve-pkg-maps@1.0.0: {} + + reusify@1.1.0: {} + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + slash@3.0.0: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + tsc-alias@1.8.16: + dependencies: + chokidar: 3.6.0 + commander: 9.5.0 + get-tsconfig: 4.13.0 + globby: 11.1.0 + mylas: 2.1.14 + normalize-path: 3.0.0 + plimit-lit: 1.6.1 + + typescript@5.9.3: {} diff --git a/tagcanvas.js b/src/tagcanvas.js similarity index 100% rename from tagcanvas.js rename to src/tagcanvas.js diff --git a/src/tagcanvas.ts b/src/tagcanvas.ts new file mode 100644 index 0000000..d0b3291 --- /dev/null +++ b/src/tagcanvas.ts @@ -0,0 +1,2465 @@ +/** + * TagCanvas 2.11 (TypeScript port) + * + * Ported from the bundled `src/tagcanvas.js` implementation. + * The project is distributed under LGPL-3.0-only (see repository LICENSE). + */ + +import { defaultTagCanvasOptions } from '@/tagcanvas/options'; +import { Clamp, Defined, IsObject, Nop, Shuffle, SortList, TimeNow } from '@/tagcanvas/util'; +import { MakeVector, Matrix, Project, Vector } from '@/tagcanvas/geometry'; +import { + PointsOnCylinderH, + PointsOnCylinderV, + PointsOnRingH, + PointsOnRingV, + PointsOnSphere, +} from '@/tagcanvas/shapes'; +import type { TagCanvasOptions, ThreeDCoord, XY, XYZ } from '@/tagcanvas/types'; + +export type { TagCanvasOptions } from '@/tagcanvas/types'; +export { defaultTagCanvasOptions }; + + +const hexlookup1: Record = { + 0: '0,', + 1: '17,', + 2: '34,', + 3: '51,', + 4: '68,', + 5: '85,', + 6: '102,', + 7: '119,', + 8: '136,', + 9: '153,', + a: '170,', + A: '170,', + b: '187,', + B: '187,', + c: '204,', + C: '204,', + d: '221,', + D: '221,', + e: '238,', + E: '238,', + f: '255,', + F: '255,', +}; + +const hexlookup2: Record = (() => { + const out: Record = {}; + for (let i = 0; i < 256; ++i) { + let j = i.toString(16); + if (i < 16) j = '0' + j; + out[j] = out[j.toUpperCase()] = i.toString() + ','; + } + return out; +})(); + +// NOTE: Mutable caches / registries are kept on TagCanvas (static) or instances +// to avoid module-level shared state that can break atomic operations. + +function SetupAudio(): AudioContext | undefined { + // @ts-expect-error: webkitAudioContext exists on some browsers + const ac = window.AudioContext || window.webkitAudioContext; + try { + if (!ac) { + TagCanvas.sharedAudioContext = 'off'; + return undefined; + } + TagCanvas.sharedAudioContext = new ac(); + if (!TagCanvas.sharedAudioContext) { + TagCanvas.sharedAudioContext = 'off'; + return undefined; + } + return TagCanvas.sharedAudioContext; + } catch { + TagCanvas.sharedAudioContext = 'off'; + return undefined; + } +} + +function AudioIcon( + mute: any, + c: CanvasRenderingContext2D, + size: number, + offsetx: number, + offsety: number, + stroke: number, + colour: string, +) { + const x = offsetx, + y = offsety, + s = size * 0.01, + w = 80 * s, + h = 100 * s, + d = 40 * s, + e = 30 * s; + const f = e / 2; + const x2 = x + w, + x1 = x2 - d; + const y3 = y + h, + y2 = y3 - e, + y1 = y + e, + y4 = y + h / 2; + c.setTransform(1, 0, 0, 1, 0, 0); + c.setLineDash([]); + c.globalAlpha = 1; + c.strokeStyle = colour; + c.lineWidth = stroke; + c.lineJoin = 'round'; + c.beginPath(); + c.moveTo(x1, y1); + c.lineTo(x1, y2); + c.moveTo(x2, y3); + c.lineTo(x1, y2); + c.lineTo(x, y2); + c.lineTo(x, y1); + c.lineTo(x1, y1); + c.lineTo(x2, y); + if (mute) { + c.lineTo(x2, y1); + c.moveTo(x2, y2); + c.lineTo(x2, y3); + c.moveTo(x2 - f, y4 - f); + c.lineTo(x2 + f, y4 + f); + c.moveTo(x2 + f, y4 - f); + c.lineTo(x2 - f, y4 + f); + c.stroke(); + return; + } + c.closePath(); + c.stroke(); +} + +function CentreImage(t: any) { + const i = new Image(); + i.onload = function () { + const dx = i.width / 2; + const dy = i.height / 2; + t.centreFunc = function (c: CanvasRenderingContext2D, _w: number, _h: number, cx: number, cy: number) { + c.setTransform(1, 0, 0, 1, 0, 0); + c.globalAlpha = 1; + c.drawImage(i, cx - dx, cy - dy); + }; + }; + i.src = t.centreImage; +} + +function SetAlpha(c: string, a: number) { + let d = c; + let p1: number; + let p2: number; + const ae = (a * 1).toPrecision(3) + ')'; + if (c[0] === '#') { + if (!TagCanvas.hexlookup3[c]) { + if (c.length === 4) { + TagCanvas.hexlookup3[c] = + 'rgba(' + hexlookup1[c[1]] + hexlookup1[c[2]] + hexlookup1[c[3]]; + } else { + TagCanvas.hexlookup3[c] = + 'rgba(' + + hexlookup2[c.substr(1, 2)] + + hexlookup2[c.substr(3, 2)] + + hexlookup2[c.substr(5, 2)]; + } + } + d = TagCanvas.hexlookup3[c] + ae; + } else if (c.substr(0, 4) === 'rgb(' || c.substr(0, 4) === 'hsl(') { + d = c.replace('(', 'a(').replace(')', ',' + ae); + } else if (c.substr(0, 5) === 'rgba(' || c.substr(0, 5) === 'hsla(') { + p1 = c.lastIndexOf(',') + 1; + p2 = c.indexOf(')'); + a *= parseFloat(c.substring(p1, p2)); + d = c.substr(0, p1) + a.toPrecision(3) + ')'; + } + return d; +} + +function NewCanvas(w: number, h: number) { + if ((window as any).G_vmlCanvasManager) return null; + const c = document.createElement('canvas'); + c.width = w; + c.height = h; + return c; +} + +function ShadowAlphaBroken() { + let cv = NewCanvas(3, 3); + if (!cv) return false; + const c = cv.getContext('2d')!; + c.strokeStyle = '#000'; + c.shadowColor = '#fff'; + c.shadowBlur = 3; + c.globalAlpha = 0; + c.strokeRect(2, 2, 2, 2); + c.globalAlpha = 1; + const i = c.getImageData(2, 2, 1, 1); + cv = null; + return i.data[0] > 0; +} + +function SetGradient(c: CanvasRenderingContext2D, l: number, o: number, g: any) { + const gd = c.createLinearGradient(0, 0, l, 0); + for (const i in g) gd.addColorStop(1 - (i as any), g[i]); + c.fillStyle = gd; + c.fillRect(0, o, l, 1); +} + +function FindGradientColour(tc: any, p: number, r?: number) { + const l = 1024; + let h = 1; + let gl = tc.weightGradient; + let cv: HTMLCanvasElement | null; + let c: CanvasRenderingContext2D; + let d: Uint8ClampedArray; + + if (tc.gCanvas) { + c = tc.gCanvas.getContext('2d'); + h = tc.gCanvas.height; + } else { + if (IsObject(gl[0])) h = gl.length; + else gl = [gl]; + tc.gCanvas = cv = NewCanvas(l, h); + if (!cv) return null; + c = cv.getContext('2d')!; + for (let i = 0; i < h; ++i) SetGradient(c, l, i, gl[i]); + } + r = Math.max(Math.min(r || 0, h - 1), 0); + d = c.getImageData(~~((l - 1) * p), r, 1, 1).data; + return 'rgba(' + d[0] + ',' + d[1] + ',' + d[2] + ',' + d[3] / 255 + ')'; +} + +function TextSet( + ctxt: CanvasRenderingContext2D, + font: string, + colour: string, + strings: string[], + padx: number, + pady: number, + shadowColour: any, + shadowBlur: number, + shadowOffsets: number[], + maxWidth: any, + widths?: number[], + align?: string, +) { + const xo = + padx + + (shadowBlur || 0) + + (shadowOffsets.length && shadowOffsets[0] < 0 ? Math.abs(shadowOffsets[0]) : 0); + let yo = + pady + + (shadowBlur || 0) + + (shadowOffsets.length && shadowOffsets[1] < 0 ? Math.abs(shadowOffsets[1]) : 0); + ctxt.font = font; + ctxt.textBaseline = 'top'; + ctxt.fillStyle = colour; + shadowColour && (ctxt.shadowColor = shadowColour); + shadowBlur && (ctxt.shadowBlur = shadowBlur); + shadowOffsets.length && ((ctxt.shadowOffsetX = shadowOffsets[0]), (ctxt.shadowOffsetY = shadowOffsets[1])); + for (let i = 0; i < strings.length; ++i) { + let xc = 0; + if (widths) { + if ('right' === align) xc = maxWidth - widths[i]; + else if ('centre' === align) xc = (maxWidth - widths[i]) / 2; + } + ctxt.fillText(strings[i], xo + xc, yo); + yo += parseInt(font); + } +} + +function RRect(c: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, r: number, s?: boolean) { + if (r) { + c.beginPath(); + c.moveTo(x, y + h - r); + c.arcTo(x, y, x + r, y, r); + c.arcTo(x + w, y, x + w, y + r, r); + c.arcTo(x + w, y + h, x + w - r, y + h, r); + c.arcTo(x, y + h, x, y + h - r, r); + c.closePath(); + (c as any)[s ? 'stroke' : 'fill'](); + } else { + (c as any)[s ? 'strokeRect' : 'fillRect'](x, y, w, h); + } +} + +class TextCanvas { + image?: CanvasImageSource; + iwidth?: number; + iheight?: number; + ipos?: any; + ipad?: number; + iscale?: number; + ialign?: any; + ivalign?: any; + + constructor( + public strings: string[], + public font: string, + public width: number, + public height: number, + public maxWidth: number, + public stringWidths: number[], + public align: any, + public valign: any, + public scale: number, + ) {} + + SetImage(image: CanvasImageSource, w: number, h: number, position: any, padding: number, align: any, valign: any, scale: number) { + this.image = image; + this.iwidth = w * this.scale; + this.iheight = h * this.scale; + this.ipos = position; + this.ipad = padding * this.scale; + this.iscale = scale; + this.ialign = align; + this.ivalign = valign; + } + + Align(size: number, space: number, a: any) { + let pos = 0; + if (a === 'right' || a === 'bottom') pos = space - size; + else if (a !== 'left' && a !== 'top') pos = (space - size) / 2; + return pos; + } + + Create( + colour: any, + bgColour: any, + bgOutline: any, + bgOutlineThickness: any, + shadowColour: any, + shadowBlur: any, + shadowOffsets: any, + padding: any, + radius: any, + ) { + let cv: HTMLCanvasElement | null; + let cw: number; + let ch: number; + let c: CanvasRenderingContext2D; + let x1: number, + x2: number, + y1: number, + y2: number; + let offx: number, + offy: number; + let ix: number, + iy: number, + iw: number, + ih: number; + let rr: number; + const sox = Math.abs(shadowOffsets[0]); + const soy = Math.abs(shadowOffsets[1]); + let shadowcv: HTMLCanvasElement | null | undefined; + let shadowc: CanvasRenderingContext2D | undefined; + + padding = Math.max(padding, sox + shadowBlur, soy + shadowBlur); + x1 = 2 * (padding + bgOutlineThickness); + y1 = 2 * (padding + bgOutlineThickness); + cw = this.width + x1; + ch = this.height + y1; + offx = offy = padding + bgOutlineThickness; + + if (this.image) { + ix = iy = padding + bgOutlineThickness; + iw = this.iwidth!; + ih = this.iheight!; + if (this.ipos === 'top' || this.ipos === 'bottom') { + if (iw < this.width) ix += this.Align(iw, this.width, this.ialign); + else offx += this.Align(this.width, iw, this.align); + if (this.ipos === 'top') offy += ih + this.ipad!; + else iy += this.height + this.ipad!; + cw = Math.max(cw, iw + x1); + ch += ih + this.ipad!; + } else { + if (ih < this.height) iy += this.Align(ih, this.height, this.ivalign); + else offy += this.Align(this.height, ih, this.valign); + if (this.ipos === 'right') ix += this.width + this.ipad!; + else offx += iw + this.ipad!; + cw += iw + this.ipad!; + ch = Math.max(ch, ih + y1); + } + } + + cv = NewCanvas(cw, ch); + if (!cv) return null; + + x1 = y1 = bgOutlineThickness / 2; + x2 = cw - bgOutlineThickness; + y2 = ch - bgOutlineThickness; + rr = Math.min(radius, x2 / 2, y2 / 2); + c = cv.getContext('2d')!; + + if (bgColour) { + c.fillStyle = bgColour; + RRect(c, x1, y1, x2, y2, rr); + } + if (bgOutlineThickness) { + c.strokeStyle = bgOutline; + c.lineWidth = bgOutlineThickness; + RRect(c, x1, y1, x2, y2, rr, true); + } + + if (shadowBlur || sox || soy) { + shadowcv = NewCanvas(cw, ch); + if (shadowcv) { + shadowc = c; + c = shadowcv.getContext('2d')!; + } + } + + TextSet(c, this.font, colour, this.strings, offx, offy, 0, 0, [], this.maxWidth, this.stringWidths, this.align); + + if (this.image) c.drawImage(this.image, ix!, iy!, iw!, ih!); + + if (shadowc && shadowcv) { + c = shadowc; + shadowColour && (c.shadowColor = shadowColour); + shadowBlur && (c.shadowBlur = shadowBlur); + c.shadowOffsetX = shadowOffsets[0]; + c.shadowOffsetY = shadowOffsets[1]; + c.drawImage(shadowcv, 0, 0); + } + return cv; + } +} + +function ExpandImage(i: CanvasImageSource, w: number, h: number) { + const cv = NewCanvas(w, h); + if (!cv) return null; + const c = cv.getContext('2d')!; + c.drawImage(i as any, (w - (i as any).width) / 2, (h - (i as any).height) / 2); + return cv; +} + +function ScaleImage(i: CanvasImageSource, w: number, h: number) { + const cv = NewCanvas(w, h); + if (!cv) return null; + const c = cv.getContext('2d')!; + c.drawImage(i, 0, 0, w, h); + return cv; +} + +function AddBackgroundToImage( + i: any, + w: number, + h: number, + scale: number, + colour: any, + othickness: number, + ocolour: any, + padding: number, + radius: number, + ofill?: any, +) { + const cw = w + ((2 * padding + othickness) * scale); + const ch = h + ((2 * padding + othickness) * scale); + const cv = NewCanvas(cw, ch); + if (!cv) return null; + + othickness *= scale; + radius *= scale; + let x1 = othickness / 2; + let y1 = x1; + const x2 = cw - othickness; + const y2 = ch - othickness; + padding = padding * scale + x1; + const c = cv.getContext('2d')!; + const rr = Math.min(radius, x2 / 2, y2 / 2); + + + if (colour) { + c.fillStyle = colour; + RRect(c, x1, y1, x2, y2, rr); + } + if (othickness) { + c.strokeStyle = ocolour; + c.lineWidth = othickness; + RRect(c, x1, y1, x2, y2, rr, true); + } + + if (ofill) { + const ocanvas2 = NewCanvas(cw, ch); + const cc = ocanvas2?.getContext('2d')!; + cc.drawImage(i, padding, padding, w, h); + cc.globalCompositeOperation = 'source-in'; + cc.fillStyle = ocolour; + cc.fillRect(0, 0, cw, ch); + cc.globalCompositeOperation = 'destination-over'; + cc.drawImage(cv, 0, 0); + cc.globalCompositeOperation = 'source-over'; + c.drawImage(ocanvas2!, 0, 0); + } else { + c.drawImage(i, padding, padding, i.width, i.height); + } + return { image: cv, width: cw / scale, height: ch / scale }; +} + +function RoundImage(i: any, r: any, iw: number, ih: number, s: number) { + const cv = NewCanvas(iw, ih); + if (!cv) return null; + let r1 = parseFloat(r); + const l = Math.max(iw, ih); + if (r.indexOf('%') > 0) r1 = (l * r1) / 100; + else r1 = r1 * s; + const c = cv.getContext('2d')!; + c.globalCompositeOperation = 'source-over'; + c.fillStyle = '#fff'; + if (r1 >= l / 2) { + r1 = Math.min(iw, ih) / 2; + c.beginPath(); + c.moveTo(iw / 2, ih / 2); + c.arc(iw / 2, ih / 2, r1, 0, 2 * Math.PI, false); + c.fill(); + c.closePath(); + } else { + r1 = Math.min(iw / 2, ih / 2, r1); + RRect(c, 0, 0, iw, ih, r1, true); + c.fill(); + } + c.globalCompositeOperation = 'source-in'; + c.drawImage(i, 0, 0, iw, ih); + return cv; +} + +function AddShadowToImage(i: any, w: number, h: number, scale: number, sc: any, sb: number, so: number[]) { + const sw = Math.abs(so[0]); + const sh = Math.abs(so[1]); + const cw = w + (sw > sb ? sw + sb : sb * 2) * scale; + const ch = h + (sh > sb ? sh + sb : sb * 2) * scale; + const xo = scale * ((sb || 0) + (so[0] < 0 ? sw : 0)); + const yo = scale * ((sb || 0) + (so[1] < 0 ? sh : 0)); + const cv = NewCanvas(cw, ch); + if (!cv) return null; + const c = cv.getContext('2d')!; + sc && (c.shadowColor = sc); + sb && (c.shadowBlur = sb * scale); + so && ((c.shadowOffsetX = so[0] * scale), (c.shadowOffsetY = so[1] * scale)); + c.drawImage(i, xo, yo, w, h); + return { image: cv, width: cw / scale, height: ch / scale }; +} + +function FindTextBoundingBox(s: string[], f: string, ht: number) { + const w = parseInt((s.toString().length * ht) as any); + const h = parseInt((ht * 2 * s.length) as any); + let cv = NewCanvas(w, h); + if (!cv) return null; + const c = cv.getContext('2d')!; + c.fillStyle = '#000'; + c.fillRect(0, 0, w, h); + TextSet(c, ht + 'px ' + f, '#fff', s, 0, 0, 0, 0, [], 'centre'); + const idata = c.getImageData(0, 0, w, h); + const w1 = idata.width; + const h1 = idata.height; + const ex = { + min: { x: w1, y: h1 }, + max: { x: -1, y: -1 }, + }; + for (let y = 0; y < h1; ++y) { + for (let x = 0; x < w1; ++x) { + const i = (y * w1 + x) * 4; + if (idata.data[i + 1] > 0) { + if (x < ex.min.x) ex.min.x = x; + if (x > ex.max.x) ex.max.x = x; + if (y < ex.min.y) ex.min.y = y; + if (y > ex.max.y) ex.max.y = y; + } + } + } + if (w1 !== w) { + ex.min.x *= w / w1; + ex.max.x *= w / w1; + } + if (h1 !== h) { + ex.min.y *= w / h1; + ex.max.y *= w / h1; + } + cv = null; + return ex; +} + +function FixFont(f: string) { + return "'" + f.replace(/(\'|\")/g, '').replace(/\s*,\s*/g, "', '") + "'"; +} + +function AddHandler(h: string, f: EventListener, e?: EventTarget | Document) { + const target: any = e || document; + if (target.addEventListener) target.addEventListener(h, f, false); + else target.attachEvent?.('on' + h, f); +} + +function RemoveHandler(h: string, f: EventListener, e?: EventTarget | Document) { + const target: any = e || document; + if (target.removeEventListener) target.removeEventListener(h, f); + else target.detachEvent?.('on' + h, f); +} + +function AddImage(i: any, o: any, alt: any, t: any, tc: any) { + const s = tc.imageScale; + let mscale: number; + let ic: any; + let bc: any; + let oc: any; + let iw: any; + let ih: any; + + if (!o.complete) return AddHandler('load', function () { AddImage(i, o, alt, t, tc); }, o); + if (!i.complete) return AddHandler('load', function () { AddImage(i, o, alt, t, tc); }, i); + if (alt && !alt.complete) return AddHandler('load', function () { AddImage(i, o, alt, t, tc); }, alt); + + o.width = o.width; + o.height = o.height; + + if (s) { + i.width = o.width * s; + i.height = o.height * s; + } + t.iw = i.width; + t.ih = i.height; + + if (tc.txtOpt) { + ic = i; + mscale = tc.zoomMax * tc.txtScale; + iw = t.iw * mscale; + ih = t.ih * mscale; + if (iw < o.naturalWidth || ih < o.naturalHeight) { + ic = ScaleImage(i, iw, ih); + if (ic) t.fimage = ic; + } else { + iw = t.iw; + ih = t.ih; + mscale = 1; + } + + if (parseFloat(tc.imageRadius)) t.image = t.fimage = i = RoundImage(t.image, tc.imageRadius, iw, ih, mscale); + + if (!t.HasText()) { + if (tc.shadow) { + ic = AddShadowToImage(t.image, iw, ih, mscale, tc.shadow, tc.shadowBlur, tc.shadowOffset); + if (ic) { + t.fimage = ic.image; + t.w = ic.width; + t.h = ic.height; + } + } + if (tc.bgColour || tc.bgOutlineThickness) { + bc = tc.bgColour === 'tag' ? GetProperty(t.a, 'background-color') : tc.bgColour; + oc = tc.bgOutline === 'tag' ? GetProperty(t.a, 'color') : (tc.bgOutline || tc.textColour); + iw = t.fimage.width; + ih = t.fimage.height; + if (tc.outlineMethod === 'colour') { + ic = AddBackgroundToImage(t.fimage, iw, ih, mscale, bc, tc.bgOutlineThickness, t.outline.colour, tc.padding, tc.bgRadius, 1); + if (ic) t.oimage = ic.image; + } + ic = AddBackgroundToImage(t.fimage, iw, ih, mscale, bc, tc.bgOutlineThickness, oc, tc.padding, tc.bgRadius); + if (ic) { + t.fimage = ic.image; + t.w = ic.width; + t.h = ic.height; + } + } + if (tc.outlineMethod === 'size') { + if (tc.outlineIncrease > 0) { + t.iw += 2 * tc.outlineIncrease; + t.ih += 2 * tc.outlineIncrease; + iw = mscale * t.iw; + ih = mscale * t.ih; + ic = ScaleImage(t.fimage, iw, ih); + t.oimage = ic; + t.fimage = ExpandImage(t.fimage, t.oimage.width, t.oimage.height); + } else { + iw = mscale * (t.iw + 2 * tc.outlineIncrease); + ih = mscale * (t.ih + 2 * tc.outlineIncrease); + ic = ScaleImage(t.fimage, iw, ih); + t.oimage = ExpandImage(ic, t.fimage.width, t.fimage.height); + } + } + } + } + + t.alt = alt; + t.Init(); +} + +function GetProperty(e: Element, p: string) { + const dv: any = (document as any).defaultView; + const pc = p.replace(/\-([a-z])/g, function (a) { + return a.charAt(1).toUpperCase(); + }); + return ( + (dv && dv.getComputedStyle && dv.getComputedStyle(e, null).getPropertyValue(p)) || + ((e as any).currentStyle && (e as any).currentStyle[pc]) + ); +} + +function FindWeight(a: any, wFrom: any, tHeight: any) { + let w = 1; + let p: any; + if (wFrom) { + w = 1 * (a.getAttribute(wFrom) || tHeight); + } else if ((p = GetProperty(a, 'font-size'))) { + w = + (p.indexOf('px') > -1 && p.replace('px', '') * 1) || + (p.indexOf('pt') > -1 && p.replace('pt', '') * 1.25) || + p * 3.3; + } + return w; +} + +function EventToCanvasId(e: any) { + if (e?.target && Defined(e.target.id)) return e.target.id; + if (e?.currentTarget && Defined(e.currentTarget.id)) return e.currentTarget.id; + return e?.srcElement?.parentNode?.id; +} + +function AbsPos(id: string): XY { + const e = document.getElementById(id)!; + const r = e.getBoundingClientRect(); + const dd = document.documentElement; + const b = document.body; + const w = window; + const xs = w.pageXOffset || dd.scrollLeft; + const ys = w.pageYOffset || dd.scrollTop; + const xo = dd.clientLeft || b.clientLeft; + const yo = dd.clientTop || b.clientTop; + return { x: r.left + xs - xo, y: r.top + ys - yo }; +} + +function EventXY(e: any, c: HTMLCanvasElement): XY | undefined { + let xy: XY | undefined; + const xmul = parseInt(GetProperty(c, 'width') as any) / c.width; + const ymul = parseInt(GetProperty(c, 'height') as any) / c.height; + + if (Defined(e.offsetX)) { + xy = { x: e.offsetX, y: e.offsetY }; + } else { + const p = AbsPos(c.id); + if (Defined(e.changedTouches)) e = e.changedTouches[0]; + if (e.pageX) xy = { x: e.pageX - p.x, y: e.pageY - p.y }; + } + if (xy && xmul && ymul) { + xy.x /= xmul; + xy.y /= ymul; + } + return xy; +} + +function MouseOut(e: any) { + const cv = e.target || e.fromElement?.parentNode; + const tc = (TagCanvas as any).tc[cv.id]; + if (tc) { + tc.mx = tc.my = -1; + tc.UnFreeze(); + tc.EndDrag(); + } +} + +function MouseMove(e: any) { + const t: any = TagCanvas; + const tg = EventToCanvasId(e); + for (const i in t.tc) { + const tc = t.tc[i]; + if (tc.tttimer) { + clearTimeout(tc.tttimer); + tc.tttimer = null; + } + } + if (tg && t.tc[tg]) { + const tc = t.tc[tg]; + const p = EventXY(e, tc.canvas); + if (p) { + tc.mx = p.x; + tc.my = p.y; + tc.Drag(e, p); + } + tc.drawn = 0; + } +} + +function MouseDown(e: any) { + const t: any = TagCanvas; + const cb = (document as any).addEventListener ? 0 : 1; + const tg = EventToCanvasId(e); + if (tg && e.button === cb && t.tc[tg]) { + t.tc[tg].BeginDrag(e); + } +} + +function MouseUp(e: any) { + const t: any = TagCanvas; + const cb = (document as any).addEventListener ? 0 : 1; + const tg = EventToCanvasId(e); + if (tg && e.button === cb && t.tc[tg]) { + const tc = t.tc[tg]; + MouseMove(e); + if (!tc.EndDrag() && !tc.touchState) tc.Clicked(e); + } +} + +function TouchDown(e: any) { + const tg = EventToCanvasId(e); + const tc = tg && (TagCanvas as any).tc[tg]; + if (tc && e.changedTouches) { + if (e.touches.length === 1 && tc.touchState === 0) { + tc.touchState = 1; + tc.BeginDrag(e); + const p = EventXY(e, tc.canvas); + if (p) { + tc.mx = p.x; + tc.my = p.y; + tc.drawn = 0; + } + } else if (e.targetTouches.length === 2 && tc.pinchZoom) { + tc.touchState = 3; + tc.EndDrag(); + tc.BeginPinch(e); + } else { + tc.EndDrag(); + tc.EndPinch(); + tc.touchState = 0; + } + } +} + +function TouchUp(e: any) { + const tg = EventToCanvasId(e); + const tc = tg && (TagCanvas as any).tc[tg]; + if (tc && e.changedTouches) { + switch (tc.touchState) { + case 1: + tc.Draw(); + tc.Clicked(); + break; + case 2: + tc.EndDrag(); + break; + case 3: + tc.EndPinch(); + break; + } + tc.touchState = 0; + } +} + +function TouchMove(e: any) { + const t: any = TagCanvas; + const tg = EventToCanvasId(e); + for (const i in t.tc) { + const tc = t.tc[i]; + if (tc.tttimer) { + clearTimeout(tc.tttimer); + tc.tttimer = null; + } + } + const tc = tg && t.tc[tg]; + if (tc && e.changedTouches && tc.touchState) { + switch (tc.touchState) { + case 1: + case 2: { + const p = EventXY(e, tc.canvas); + if (p) { + tc.mx = p.x; + tc.my = p.y; + if (tc.Drag(e, p)) tc.touchState = 2; + } + break; + } + case 3: + tc.Pinch(e); + break; + } + tc.drawn = 0; + } +} + +function MouseWheel(e: any) { + const t: any = TagCanvas; + const tg = EventToCanvasId(e); + if (tg && t.tc[tg]) { + e.cancelBubble = true; + e.returnValue = false; + e.preventDefault && e.preventDefault(); + t.tc[tg].Wheel((e.wheelDelta || e.detail) > 0); + } +} + +function Scroll(_e: any) { + const t: any = TagCanvas; + clearTimeout(t.scrollTimer); + for (const i in t.tc) t.tc[i].Pause(); + t.scrollTimer = setTimeout(function () { + const t: any = TagCanvas; + for (const i in t.tc) t.tc[i].Resume(); + }, t.scrollPause); +} + +function DrawCanvas() { + DrawCanvasRAF(TimeNow()); +} + +function DrawCanvasRAF(t?: number) { + const tc: any = (TagCanvas as any).tc; + (TagCanvas as any).NextFrame((TagCanvas as any).interval); + t = t || TimeNow(); + for (const i in tc) tc[i].Draw(t); +} + +function NextFrameRAF() { + requestAnimationFrame(DrawCanvasRAF); +} + +function NextFrameTimeout(iv: number) { + setTimeout(DrawCanvas, iv); +} + +class TextSplitter { + br = 0; + line: string[] = []; + text: string[] = []; + original: string; + constructor(public e: any) { + this.original = e.innerText || e.textContent; + } + + Empty() { + for (let i = 0; i < this.text.length; ++i) if (this.text[i].length) return false; + return true; + } + + Lines(e?: any) { + const r = e ? 1 : 0; + e = e || this.e; + const cn = e.childNodes; + const cl = cn.length; + for (let i = 0; i < cl; ++i) { + if (cn[i].nodeName === 'BR') { + this.text.push(this.line.join(' ')); + this.br = 1; + } else if (cn[i].nodeType === 3) { + if (this.br) { + this.line = [cn[i].nodeValue]; + this.br = 0; + } else { + this.line.push(cn[i].nodeValue); + } + } else { + this.Lines(cn[i]); + } + } + r || this.br || this.text.push(this.line.join(' ')); + return this.text; + } + + SplitWidth(w: number, c: CanvasRenderingContext2D, f: string, h: number) { + const text: string[] = []; + c.font = h + 'px ' + f; + for (let i = 0; i < this.text.length; ++i) { + const words = this.text[i].split(/\s+/); + this.line = [words[0]]; + for (let j = 1; j < words.length; ++j) { + if (c.measureText(this.line.join(' ') + ' ' + words[j]).width > w) { + text.push(this.line.join(' ')); + this.line = [words[j]]; + } else { + this.line.push(words[j]); + } + } + text.push(this.line.join(' ')); + } + this.text = text; + return this.text; + } +} + +class Outline { + ts: number | null = null; + x = 0; + y = 0; + w = 0; + h = 0; + sc = 1; + z = 0; + pulse = 1; + pulsate: boolean; + colour: any; + adash: number; + agap: number; + aspeed: number; + radius: number; + drawFunc?: (...args: any[]) => any; + RealPreDraw?: any; + + constructor( + public tc: any, + public tag: any, + ) { + this.pulsate = tc.pulsateTo < 1; + this.colour = tc.outlineColour; + this.adash = ~~tc.outlineDash; + this.agap = ~~tc.outlineDashSpace || this.adash; + this.aspeed = tc.outlineDashSpeed * 1; + if (this.colour === 'tag') this.colour = GetProperty(tag.a, 'color'); + else if (this.colour === 'tagbg') this.colour = GetProperty(tag.a, 'background-color'); + this.Draw = this.pulsate ? this.DrawPulsate : this.DrawSimple; + this.radius = tc.outlineRadius | 0; + this.SetMethod(tc.outlineMethod, tc.altImage); + } + + SetMethod(om: any, alt: any) { + const methods: Record = { + block: ['PreDraw', 'DrawBlock'], + colour: ['PreDraw', 'DrawColour'], + outline: ['PostDraw', 'DrawOutline'], + classic: ['LastDraw', 'DrawOutline'], + size: ['PreDraw', 'DrawSize'], + none: ['LastDraw'], + }; + const funcs = methods[om] || methods.outline; + if (om === 'none') { + this.Draw = function () { + return 1; + }; + } else { + this.drawFunc = (this as any)[funcs[1]!]; + } + (this as any)[funcs[0]] = this.Draw; + if (alt) { + this.RealPreDraw = this.PreDraw; + this.PreDraw = this.DrawAlt; + } + } + + Update(x: number, y: number, w: number, h: number, sc: number, z: number, xo: number, yo: number) { + const o = this.tc.outlineOffset; + const o2 = 2 * o; + this.x = sc * x + xo - o; + this.y = sc * y + yo - o; + this.w = sc * w + o2; + this.h = sc * h + o2; + this.sc = sc; + this.z = z; + } + + Ants(c: CanvasRenderingContext2D) { + if (!this.adash) return; + let l = this.adash; + let g = this.agap; + let s = this.aspeed; + const length = l + g; + let l1 = 0, + l2 = l, + g1 = g, + g2 = 0; + if (s) { + let seq = Math.abs(s) * ((TimeNow() - (this.ts as any)) / 50); + if (s < 0) seq = 8.64e6 - seq; + s = ~~seq % length; + } + let ants: number[]; + if (s) { + if (l >= s) { + l1 = l - s; + l2 = s; + } else { + g1 = length - s; + g2 = g - g1; + } + ants = [l1, g1, l2, g2]; + } else { + ants = [l, g]; + } + c.setLineDash(ants); + } + + DrawOutline(c: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, colour: any) { + const r = Math.min(this.radius, h / 2, w / 2); + c.strokeStyle = colour; + this.Ants(c); + RRect(c, x, y, w, h, r, true); + } + + DrawSize(c: CanvasRenderingContext2D, _x: number, _y: number, _w: number, _h: number, _colour: any, tag: any, x1: number, y1: number) { + const tw = tag.w; + const th = tag.h; + let i: any; + if (this.pulsate) { + let sc: number; + if (tag.image) sc = (tag.image.height + this.tc.outlineIncrease) / tag.image.height; + else sc = tag.oscale; + i = tag.fimage || tag.image; + const m = 1 + (sc - 1) * (1 - this.pulse); + tag.h *= m; + tag.w *= m; + } else { + i = tag.oimage; + } + tag.alpha = 1; + tag.Draw(c, x1, y1, i); + tag.h = th; + tag.w = tw; + return 1; + } + + DrawColour(c: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, colour: any, tag: any, x1: number, y1: number) { + if (tag.oimage) { + if (this.pulse < 1) { + tag.alpha = 1 - Math.pow(this.pulse, 2); + tag.Draw(c, x1, y1, tag.fimage); + tag.alpha = this.pulse; + } else { + tag.alpha = 1; + } + tag.Draw(c, x1, y1, tag.oimage); + return 1; + } + return (this as any)[tag.image ? 'DrawColourImage' : 'DrawColourText'](c, x, y, w, h, colour, tag, x1, y1); + } + + DrawColourText(c: CanvasRenderingContext2D, _x: number, _y: number, _w: number, _h: number, colour: any, tag: any, x1: number, y1: number) { + const normal = tag.colour; + tag.colour = colour; + tag.alpha = 1; + tag.Draw(c, x1, y1); + tag.colour = normal; + return 1; + } + + DrawColourImage(c: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, colour: any, tag: any, x1: number, y1: number) { + const ccanvas = c.canvas; + const fx = ~~Math.max(x, 0); + const fy = ~~Math.max(y, 0); + const fw = (Math.min(ccanvas.width - fx, w) + 0.5) | 0; + const fh = (Math.min(ccanvas.height - fy, h) + 0.5) | 0; + + let ocanvas = this.tc.outlineCanvas as HTMLCanvasElement | null | undefined; + if (ocanvas) { + ocanvas.width = fw; + ocanvas.height = fh; + } else { + ocanvas = NewCanvas(fw, fh); + this.tc.outlineCanvas = ocanvas; + } + if (!ocanvas) { + this.SetMethod('outline', undefined); + return 0; + } + const cc = ocanvas.getContext('2d')!; + cc.drawImage(ccanvas, fx, fy, fw, fh, 0, 0, fw, fh); + c.clearRect(fx, fy, fw, fh); + tag.alpha = this.pulsate ? 1 - Math.pow(this.pulse, 2) : 1; + tag.Draw(c, x1, y1); + c.setTransform(1, 0, 0, 1, 0, 0); + c.save(); + c.beginPath(); + c.rect(fx, fy, fw, fh); + c.clip(); + c.globalCompositeOperation = 'source-in'; + c.fillStyle = colour; + c.fillRect(fx, fy, fw, fh); + c.restore(); + c.globalAlpha = 1; + c.globalCompositeOperation = 'destination-over'; + c.drawImage(ocanvas, 0, 0, fw, fh, fx, fy, fw, fh); + c.globalCompositeOperation = 'source-over'; + return 1; + } + + DrawAlt(c: CanvasRenderingContext2D, tag: any, x1: number, y1: number, ga: any, useGa: any) { + const r = (this.RealPreDraw as any)(c, tag, x1, y1, ga, useGa); + if (tag.alt) { + tag.DrawImage(c, x1, y1, tag.alt); + return 1; + } + return r; + } + + DrawBlock(c: CanvasRenderingContext2D, x: number, y: number, w: number, h: number, colour: any) { + const r = Math.min(this.radius, h / 2, w / 2); + c.fillStyle = colour; + RRect(c, x, y, w, h, r); + } + + DrawSimple(c: CanvasRenderingContext2D, tag: any, x1: number, y1: number, ga?: any, useGa?: any) { + const t = this.tc; + c.setTransform(1, 0, 0, 1, 0, 0); + c.strokeStyle = this.colour; + c.lineWidth = t.outlineThickness; + c.shadowBlur = 0; + c.shadowOffsetX = 0; + c.shadowOffsetY = 0; + c.globalAlpha = useGa ? ga : 1; + return (this.drawFunc as any)(c, this.x, this.y, this.w, this.h, this.colour, tag, x1, y1); + } + + DrawPulsate(c: CanvasRenderingContext2D, tag: any, x1: number, y1: number) { + const diff = TimeNow() - (this.ts as any); + const t = this.tc; + let ga = + t.pulsateTo + + (1 - t.pulsateTo) * (0.5 + Math.cos((2 * Math.PI * diff) / (1000 * t.pulsateTime)) / 2); + this.pulse = ga = (TagCanvas as any).Smooth(1, ga); + return this.DrawSimple(c, tag, x1, y1, ga, 1); + } + + Active(_c: CanvasRenderingContext2D, x: number, y: number) { + const a = x >= this.x && y >= this.y && x <= this.x + this.w && y <= this.y + this.h; + if (a) this.ts = this.ts || TimeNow(); + else this.ts = null; + return a; + } + + PreDraw: any = Nop; + PostDraw: any = Nop; + LastDraw: any = Nop; + Draw: any = Nop; +} + +class Tag { + image: any = null; + fimage: any; + oimage: any; + alt: any; + track: any; + gain: any; + playing: any; + stopped: any; + xformed: any; + oscale: any; + shadow?: any; + text_original: any; + line_widths: number[] = []; + title: any; + x = 0; + y = 0; + z = 0; + sc = 1; + alpha = 1; + weighted: boolean; + outline: Outline; + audio: HTMLAudioElement | null = null; + + textHeight: any; + font: any; + iw: any; + ih: any; + + constructor( + public tc: any, + public text: any, + public a: any, + v: any, + public w: any, + public h: any, + public colour?: any, + public bgColour?: any, + public bgRadius?: any, + public bgOutline?: any, + public bgOutlineThickness?: any, + public textFont?: any, + public padding?: any, + original?: any, + ) { + this.text_original = original; + this.title = a.title || null; + this.position = new Vector(v[0], v[1], v[2]); + this.colour = colour || tc.textColour; + this.bgColour = bgColour || tc.bgColour; + this.bgRadius = (bgRadius | 0) as any; + this.bgOutline = bgOutline || this.colour; + this.bgOutlineThickness = (bgOutlineThickness | 0) as any; + this.textFont = textFont || tc.textFont; + this.padding = (padding | 0) as any; + this.weighted = !tc.weight; + this.outline = new Outline(tc, this); + } + + position: Vector; + + Init(_e?: any) { + const tc = this.tc; + this.textHeight = tc.textHeight; + if (this.HasText()) this.Measure(tc.ctxt, tc); + else { + this.w = this.iw; + this.h = this.ih; + } + + this.SetShadowColour = tc.shadowAlpha ? this.SetShadowColourAlpha : this.SetShadowColourFixed; + this.SetDraw(tc); + } + + Draw: any = Nop; + + HasText() { + return this.text && this.text[0].length > 0; + } + + EqualTo(e: any) { + const i = e.getElementsByTagName('img'); + if (this.a.href !== e.href) return 0; + if (i.length) return this.image.src === i[0].src; + return (e.innerText || e.textContent) === this.text_original; + } + + SetImage(i: any) { + this.image = this.fimage = i; + } + + SetAudio(a: HTMLAudioElement) { + this.audio = a; + this.audio.load(); + } + + SetDraw(t: any) { + this.Draw = this.fimage ? (t.ie > 7 ? this.DrawImageIE : this.DrawImage) : this.DrawText; + t.noSelect && (this.CheckActive = () => null); + } + + MeasureText(c: CanvasRenderingContext2D) { + const l = this.text.length; + let w = 0; + for (let i = 0; i < l; ++i) { + const wl = c.measureText(this.text[i]).width; + this.line_widths[i] = wl; + w = Math.max(w, wl); + } + return w; + } + + Measure(c: CanvasRenderingContext2D, t: any) { + let extents = FindTextBoundingBox(this.text, this.textFont, this.textHeight); + let theight = extents ? extents.max.y + extents.min.y : this.textHeight; + c.font = this.font = this.textHeight + 'px ' + this.textFont; + let twidth = this.MeasureText(c); + + if (t.txtOpt) { + const s = t.txtScale; + let th = s * this.textHeight; + let f = th + 'px ' + this.textFont; + const soff = [s * t.shadowOffset[0], s * t.shadowOffset[1]]; + c.font = f; + const cw = this.MeasureText(c); + let tcv = new TextCanvas(this.text, f, cw + s, s * theight + s, cw, this.line_widths, t.textAlign, t.textVAlign, s); + if (this.image) tcv.SetImage(this.image, this.iw, this.ih, t.imagePosition, t.imagePadding, t.imageAlign, t.imageVAlign, t.imageScale); + let img: any = tcv.Create(this.colour, this.bgColour, this.bgOutline, s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, s * this.padding, s * this.bgRadius); + + if (t.outlineMethod === 'colour') { + this.oimage = tcv.Create(this.outline.colour, this.bgColour, this.outline.colour, s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, s * this.padding, s * this.bgRadius); + } else if (t.outlineMethod === 'size') { + const ext2 = FindTextBoundingBox(this.text, this.textFont, this.textHeight + t.outlineIncrease); + if (ext2) { + extents = ext2; + th = extents.max.y + extents.min.y; + } else { + th = this.textHeight + t.outlineIncrease; + } + f = s * (this.textHeight + t.outlineIncrease) + 'px ' + this.textFont; + c.font = f; + const cw2 = this.MeasureText(c); + tcv = new TextCanvas(this.text, f, cw2 + s, s * th + s, cw2, this.line_widths, t.textAlign, t.textVAlign, s); + if (this.image) tcv.SetImage(this.image, this.iw + t.outlineIncrease, this.ih + t.outlineIncrease, t.imagePosition, t.imagePadding, t.imageAlign, t.imageVAlign, t.imageScale); + this.oimage = tcv.Create(this.colour, this.bgColour, this.bgOutline, s * this.bgOutlineThickness, t.shadow, s * t.shadowBlur, soff, s * this.padding, s * this.bgRadius); + this.oscale = this.oimage.width / img.width; + if (t.outlineIncrease > 0) img = ExpandImage(img, this.oimage.width, this.oimage.height); + else this.oimage = ExpandImage(this.oimage, img.width, img.height); + } + + if (img) { + this.fimage = img; + twidth = this.fimage.width / s; + theight = this.fimage.height / s; + } + this.SetDraw(t); + t.txtOpt = !!this.fimage; + } + + this.h = theight; + this.w = twidth; + } + + SetFont(f: any, c: any, bc: any, boc: any) { + this.textFont = f; + this.colour = c; + this.bgColour = bc; + this.bgOutline = boc; + this.Measure(this.tc.ctxt, this.tc); + } + + SetWeight(w: any[]) { + const tc = this.tc; + const modes = tc.weightMode.split(/[, ]/); + const wl = w.length; + if (!this.HasText()) return; + this.weighted = true; + for (let s = 0; s < wl; ++s) { + const m = modes[s] || 'size'; + if (m === 'both') { + this.Weight(w[s], tc.ctxt, tc, 'size', tc.min_weight[s], tc.max_weight[s], s); + this.Weight(w[s], tc.ctxt, tc, 'colour', tc.min_weight[s], tc.max_weight[s], s); + } else { + this.Weight(w[s], tc.ctxt, tc, m, tc.min_weight[s], tc.max_weight[s], s); + } + } + this.Measure(tc.ctxt, tc); + } + + Weight(w: any, _c: any, t: any, m: any, wmin: any, wmax: any, wnum: any) { + w = Number.isNaN(w) ? 1 : w; + const nweight = (w - wmin) / (wmax - wmin); + if (m === 'colour') this.colour = FindGradientColour(t, nweight, wnum); + else if (m === 'bgcolour') this.bgColour = FindGradientColour(t, nweight, wnum); + else if (m === 'bgoutline') this.bgOutline = FindGradientColour(t, nweight, wnum); + else if (m === 'outline') this.outline.colour = FindGradientColour(t, nweight, wnum); + else if (m === 'size') { + if (t.weightSizeMin > 0 && t.weightSizeMax > t.weightSizeMin) { + this.textHeight = t.weightSize * (t.weightSizeMin + (t.weightSizeMax - t.weightSizeMin) * nweight); + } else { + this.textHeight = Math.max(1, w * t.weightSize); + } + } + } + + SetShadowColourFixed(c: any, s: any, _a: any) { + c.shadowColor = s; + } + SetShadowColourAlpha(c: any, s: any, a: any) { + c.shadowColor = SetAlpha(s, a); + } + SetShadowColour: any = this.SetShadowColourFixed; + + DrawText(c: CanvasRenderingContext2D, xoff: number, yoff: number) { + const t = this.tc; + let x = this.x; + let y = this.y; + const s = this.sc; + c.globalAlpha = this.alpha; + c.fillStyle = this.colour; + t.shadow && this.SetShadowColour(c, t.shadow, this.alpha); + c.font = this.font; + x += xoff / s; + y += yoff / s - this.h / 2; + for (let i = 0; i < this.text.length; ++i) { + let xl = x; + if (t.textAlign === 'right') xl += this.w / 2 - this.line_widths[i]; + else if (t.textAlign === 'centre') xl -= this.line_widths[i] / 2; + else xl -= this.w / 2; + c.setTransform(s, 0, 0, s, s * xl, s * y); + c.fillText(this.text[i], 0, 0); + y += this.textHeight; + } + } + + DrawImage(c: CanvasRenderingContext2D, xoff: number, yoff: number, im?: any) { + const s = this.sc; + const i = im || this.fimage; + const w = this.w; + const h = this.h; + const a = this.alpha; + const shadow = this.shadow; + c.globalAlpha = a; + shadow && this.SetShadowColour(c, shadow, a); + const x = this.x + xoff / s - w / 2; + const y = this.y + yoff / s - h / 2; + c.setTransform(s, 0, 0, s, s * x, s * y); + c.drawImage(i, 0, 0, w, h); + } + + DrawImageIE(c: CanvasRenderingContext2D, xoff: number, yoff: number) { + const i = this.fimage; + const s = this.sc; + i.width = this.w * s; + i.height = this.h * s; + const w = i.width; + const h = i.height; + const x = this.x * s + xoff - w / 2; + const y = this.y * s + yoff - h / 2; + c.setTransform(1, 0, 0, 1, 0, 0); + c.globalAlpha = this.alpha; + c.drawImage(i, x, y); + } + + Calc(m: Matrix, a: number) { + const t = this.tc; + const r = t.max_radius; + let pp: any = m.xform(this.position); + this.xformed = pp; + pp = Project(t, pp, t.stretchX, t.stretchY); + this.x = pp.x; + this.y = pp.y; + this.z = pp.z; + this.sc = pp.w; + this.alpha = a * Clamp(t.minBrightness + (t.maxBrightness - t.minBrightness) * (r - this.z) / (2 * r), 0, 1); + return this.xformed; + } + + UpdateActive(c: CanvasRenderingContext2D, xoff: number, yoff: number) { + const o = this.outline; + const w = this.w; + const h = this.h; + const x = this.x - w / 2; + const y = this.y - h / 2; + o.Update(x, y, w, h, this.sc, this.z, xoff, yoff); + return o; + } + + CheckActive(c: CanvasRenderingContext2D, xoff: number, yoff: number) { + const t = this.tc; + const o = this.UpdateActive(c, xoff, yoff); + return o.Active(c, t.mx, t.my) ? o : null; + } + + Clicked(_e?: any) { + const a = this.a; + const t = a.target; + const h = a.href; + if (t !== '' && t !== '_self') { + if ((self as any).frames[t]) { + (self as any).frames[t].document.location = h; + } else { + try { + if ((top as any).frames[t]) { + (top as any).frames[t].document.location = h; + return; + } + } catch {} + window.open(h, t); + } + return; + } + if (document.createEvent) { + const evt = document.createEvent('MouseEvents'); + evt.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null); + if (!a.dispatchEvent(evt)) return; + } else if ((a as any).fireEvent) { + if (!(a as any).fireEvent('onclick')) return; + } + (document as any).location = h; + } + + StopAudio() { + this.audio && this.playing && this.audio.pause(); + this.stopped = 1; + this.playing = 0; + } + + PlayAudio(): boolean { + if (this.tc.audioOff) return false; + if (!TagCanvas.sharedAudioContext && !SetupAudio()) return false; + const ctx = TagCanvas.sharedAudioContext; + if (!ctx || ctx === 'off') return false; + + let a: any = this.tc.audio; + let g: any = this.tc.gain; + const sus = 'suspended'; + let p: any; + + if (this.audio) { + if (!this.track) { + this.track = ctx.createMediaElementSource(this.audio); + this.gain = ctx.createGain(); + this.track.connect(this.gain); + this.gain.connect(ctx.destination); + } + a = this.audio; + g = this.gain; + if (!a.paused) return true; + } + + if (a) { + if ((ctx as any).state === sus) (ctx as any).resume(); + if ((ctx as any).state === sus) return false; + g.gain.value = Math.min(2, Math.max(0, this.tc.audioVolume * 1)); + a.currentTime = 0; + this.stopped = 0; + p = a.play(); + if (p !== undefined) { + p.then(() => { + this.stopped ? this.audio?.pause() : (this.playing = 1); + }); + } + return true; + } + return false; + } +} +export class TagCanvas { + // Shared registries / caches (kept on the class, not module scope) + static handlers: Record = {}; + static sharedAudioContext: AudioContext | 'off' | undefined; + static sharedAudioClick: ((e: Event) => void) | undefined; + static hexlookup3: Record = {}; + + static tc: Record = {}; + static options: TagCanvasOptions = { ...defaultTagCanvasOptions }; + static NextFrame: any; + static interval: number; + static started: number; + static scrollPause: any = 0; + static scrollTimer: any = null; + static loaded: any = 0; + + canvas: HTMLCanvasElement; + ctxt: CanvasRenderingContext2D; + // per-instance offscreen canvas used by outlines (avoids cross-instance interference) + outlineCanvas?: HTMLCanvasElement | null; + + // lots of dynamic options/properties; keep as any for compatibility + [k: string]: any; + + constructor(cid: string, lctr?: string, opt?: TagCanvasOptions) { + let c = document.getElementById(cid) as HTMLCanvasElement | null; + const cp: Array<'id' | 'className' | 'innerHTML'> = ['id', 'className', 'innerHTML']; + if (!c) throw new Error('Canvas not found'); + + if (Defined((window as any).G_vmlCanvasManager)) { + c = (window as any).G_vmlCanvasManager.initElement(c); + (this as any).ie = parseFloat(navigator.appVersion.split('MSIE')[1]); + } + if (c && (!c.getContext || !c.getContext('2d')!.fillText)) { + const p = document.createElement('DIV'); + for (let i = 0; i < cp.length; ++i) (p as any)[cp[i]] = (c as any)[cp[i]]; + c.parentNode!.insertBefore(p, c); + c.parentNode!.removeChild(c); + throw new Error('Canvas unsupported'); + } + + for (const i in TagCanvas.options) { + (this as any)[i] = opt && Defined((opt as any)[i]) ? (opt as any)[i] : (Defined((TagCanvas as any)[i]) ? (TagCanvas as any)[i] : (TagCanvas.options as any)[i]); + } + if (!c) throw new Error('Canvas not found'); + + this.canvas = c; + this.ctxt = c.getContext('2d')!; + this.z1 = 250 / Math.max(this.depth, 0.001); + this.z2 = this.z1 / this.zoom; + this.radius = Math.min(c.height, c.width) * 0.0075; + this.max_radius = 100; + this.max_weight = []; + this.min_weight = []; + this.textFont = this.textFont && FixFont(this.textFont); + this.textHeight *= 1; + this.imageRadius = this.imageRadius.toString(); + this.pulsateTo = Clamp(this.pulsateTo, 0, 1); + this.minBrightness = Clamp(this.minBrightness, 0, 1); + this.maxBrightness = Clamp(this.maxBrightness, this.minBrightness, 1); + this.ctxt.textBaseline = 'top'; + this.lx = (this.lock + '').indexOf('x') + 1; + this.ly = (this.lock + '').indexOf('y') + 1; + this.frozen = this.dx = this.dy = this.fixedAnim = this.touchState = 0; + this.fixedAlpha = 1; + this.source = lctr || cid; + this.repeatTags = Math.min(64, ~~this.repeatTags); + this.minTags = Math.min(200, ~~this.minTags); + if (~~this.scrollPause > 0) TagCanvas.scrollPause = ~~this.scrollPause; + else this.scrollPause = 0; + if (this.minTags > 0 && this.repeatTags < 1 && (this as any).GetTags().length) { + this.repeatTags = Math.ceil(this.minTags / (this as any).GetTags().length) - 1; + } + + this.transform = Matrix.Identity(); + this.startTime = this.time = TimeNow(); + this.mx = this.my = -1; + this.centreImage && CentreImage(this); + this.Animate = this.dragControl ? this.AnimateDrag : this.AnimatePosition; + this.animTiming = typeof (TagCanvas as any)[this.animTiming] === 'function' ? (TagCanvas as any)[this.animTiming] : TagCanvas.Smooth; + + if (this.shadowBlur || this.shadowOffset[0] || this.shadowOffset[1]) { + this.ctxt.shadowColor = this.shadow; + this.shadow = this.ctxt.shadowColor; + this.shadowAlpha = ShadowAlphaBroken(); + } else { + delete this.shadow; + } + + if (this.activeAudio === false) { + // Disable audio for this instance only + this.audioOff = true; + } else { + this.activeAudio && this.LoadAudio(); + } + + this.Load(); + if (lctr && this.hideTags) { + ((t: any) => { + if (TagCanvas.loaded) t.HideTags(); + else AddHandler('load', function () { t.HideTags(); }, window); + })(this); + } + + this.yaw = this.initial ? this.initial[0] * this.maxSpeed : 0; + this.pitch = this.initial ? this.initial[1] * this.maxSpeed : 0; + + if (this.tooltip) { + this.ctitle = c.title; + c.title = ''; + if (this.tooltip === 'native') { + this.Tooltip = this.TooltipNative; + } else { + this.Tooltip = this.TooltipDiv; + if (!this.ttdiv) { + this.ttdiv = document.createElement('div'); + this.ttdiv.className = this.tooltipClass; + this.ttdiv.style.position = 'absolute'; + this.ttdiv.style.zIndex = ((c.style.zIndex as any) || 0) + 1; + AddHandler('mouseover', function (e: any) { e.target.style.display = 'none'; }, this.ttdiv); + document.body.appendChild(this.ttdiv); + } + } + } else { + this.Tooltip = this.TooltipNone; + } + + if (!this.noMouse && !TagCanvas.handlers[cid]) { + TagCanvas.handlers[cid] = [ + ['mousemove', MouseMove], + ['mouseout', MouseOut], + ['mouseup', MouseUp], + ['touchstart', TouchDown], + ['touchend', TouchUp], + ['touchcancel', TouchUp], + ['touchmove', TouchMove], + ]; + if (this.dragControl) { + TagCanvas.handlers[cid].push(['mousedown', MouseDown]); + TagCanvas.handlers[cid].push(['selectstart', Nop]); + } + if (this.wheelZoom) { + TagCanvas.handlers[cid].push(['mousewheel', MouseWheel]); + TagCanvas.handlers[cid].push(['DOMMouseScroll', MouseWheel]); + } + if (this.scrollPause) { + TagCanvas.handlers[cid].push(['scroll', Scroll, window]); + } + for (let i = 0; i < TagCanvas.handlers[cid].length; ++i) { + const p = TagCanvas.handlers[cid][i]; + AddHandler(p[0], p[1], p[2] ? p[2] : c); + } + } + + if (!TagCanvas.started) { + TagCanvas.NextFrame = (window as any).requestAnimationFrame ? NextFrameRAF : NextFrameTimeout; + TagCanvas.interval = this.interval; + TagCanvas.NextFrame(this.interval); + TagCanvas.started = 1; + } + } + + SourceElements() { + if (document.querySelectorAll) return document.querySelectorAll('#' + this.source); + return [document.getElementById(this.source)]; + } + + HideTags() { + const el: any = this.SourceElements(); + for (let i = 0; i < el.length; ++i) el[i].style.display = 'none'; + } + + GetTags() { + const el: any = this.SourceElements(); + const tl: any[] = []; + for (let k = 0; k <= this.repeatTags; ++k) { + for (let i = 0; i < el.length; ++i) { + const etl = el[i].getElementsByTagName('a'); + for (let j = 0; j < etl.length; ++j) tl.push(etl[j]); + } + } + return tl; + } + + Message(text: string) { + const tl: any[] = []; + const tc = text.split(''); + for (let i = 0; i < tc.length; ++i) { + if (tc[i] !== ' ') { + const p = i - tc.length / 2; + const a = document.createElement('a') as HTMLAnchorElement; + a.href = '#'; + a.innerText = tc[i]; + const x = 100 * Math.sin(p / 9); + const z = -100 * Math.cos(p / 9); + const t = new Tag(this, tc[i], a, [x, 0, z], 2, 18, '#000', '#fff', 0, 0, 0, 'monospace', 2, tc[i]); + t.Init(); + tl.push(t); + } + } + return tl; + } + + AddAudio(e: any, t: any) { + if (TagCanvas.sharedAudioContext === 'off') return; + if (this.activeAudio === false) return; + const au = e.getElementsByTagName('audio'); + if (au.length) { + t.SetAudio(au[0]); + this.hasAudio = 1; + } + } + + CreateTag(e: any) { + let im: any; + let i: any; + let t: any; + let txt: any; + let ts: any; + let font: any; + let bc: any; + let boc: any; + const p = [0, 0, 0]; + + if ('text' !== this.imageMode) { + im = e.getElementsByTagName('img'); + if (im.length) { + i = new Image(); + i.src = im[0].src; + if (!this.imageMode) { + t = new Tag(this, '', e, p, 0, 0); + t.SetImage(i); + AddImage(i, im[0], im[1], t, this); + this.AddAudio(e, t); + return t; + } + } + } + if ('image' !== this.imageMode) { + ts = new TextSplitter(e); + txt = ts.Lines(); + if (!ts.Empty()) { + font = this.textFont || FixFont(GetProperty(e, 'font-family')); + if (this.splitWidth) txt = ts.SplitWidth(this.splitWidth, this.ctxt, font, this.textHeight); + bc = this.bgColour === 'tag' ? GetProperty(e, 'background-color') : this.bgColour; + boc = this.bgOutline === 'tag' ? GetProperty(e, 'color') : this.bgOutline; + } else { + ts = null; + } + } + if (ts || i) { + t = new Tag(this, txt, e, p, 2, this.textHeight + 2, this.textColour || GetProperty(e, 'color'), bc, this.bgRadius, boc, this.bgOutlineThickness, font, this.padding, ts && ts.original); + if (i) { + t.SetImage(i); + AddImage(i, im[0], im[1], t, this); + } else { + t.Init(); + } + this.AddAudio(e, t); + return t; + } + } + + UpdateTag(t: any, a: any) { + const colour = this.textColour || GetProperty(a, 'color'); + const font = this.textFont || FixFont(GetProperty(a, 'font-family')); + const bc = this.bgColour === 'tag' ? GetProperty(a, 'background-color') : this.bgColour; + const boc = this.bgOutline === 'tag' ? GetProperty(a, 'color') : this.bgOutline; + t.a = a; + t.title = a.title; + if (t.colour !== colour || t.textFont !== font || t.bgColour !== bc || t.bgOutline !== boc) t.SetFont(font, colour, bc, boc); + } + + Weight(tl: any[]) { + const ll = tl.length; + const weights: any[] = []; + const wfrom = this.weightFrom ? this.weightFrom.split(/[, ]/) : [null]; + const wl = wfrom.length; + for (let i = 0; i < ll; ++i) { + weights[i] = []; + for (let s = 0; s < wl; ++s) { + const w = FindWeight(tl[i].a, wfrom[s], this.textHeight); + if (!this.max_weight[s] || w > this.max_weight[s]) this.max_weight[s] = w; + if (!this.min_weight[s] || w < this.min_weight[s]) this.min_weight[s] = w; + weights[i][s] = w; + } + } + let valid = false; + for (let s = 0; s < wl; ++s) if (this.max_weight[s] > this.min_weight[s]) valid = true; + if (valid) for (let i = 0; i < ll; ++i) tl[i].SetWeight(weights[i]); + } + + Load() { + const tl = this.GetTags(); + let taglist: any[] = []; + const tagmap: number[] = []; + const pfuncs: any = { + sphere: PointsOnSphere, + vcylinder: PointsOnCylinderV, + hcylinder: PointsOnCylinderH, + vring: PointsOnRingV, + hring: PointsOnRingH, + }; + + if (tl.length) { + tagmap.length = tl.length; + for (let i = 0; i < tl.length; ++i) tagmap[i] = i; + this.shuffleTags && Shuffle(tagmap); + const rx = 100 * this.radiusX; + const ry = 100 * this.radiusY; + const rz = 100 * this.radiusZ; + this.max_radius = Math.max(rx, Math.max(ry, rz)); + for (let i = 0; i < tl.length; ++i) { + const t = this.CreateTag(tl[tagmap[i]]); + if (t) taglist.push(t); + } + this.weight && this.Weight(taglist); + + if (this.shapeArgs) { + this.shapeArgs[0] = taglist.length; + } else { + const shapeArgs = this.shape.toString().split(/[(),]/); + const shape = shapeArgs.shift(); + if (typeof (window as any)[shape] === 'function') this.shape = (window as any)[shape]; + else this.shape = pfuncs[shape] || pfuncs.sphere; + this.shapeArgs = [taglist.length, rx, ry, rz].concat(shapeArgs); + } + const vl = this.shape.apply(this, this.shapeArgs); + this.listLength = taglist.length; + for (let i = 0; i < taglist.length; ++i) taglist[i].position = new Vector(vl[i][0], vl[i][1], vl[i][2]); + } + if (this.noTagsMessage && !taglist.length) { + const iMode = this.imageMode && this.imageMode !== 'both' ? this.imageMode + ' ' : ''; + taglist = this.Message('No ' + iMode + 'tags'); + } + this.taglist = taglist; + } + + Update() { + const tl = this.GetTags(); + const newlist: any[] = []; + const taglist: any[] = this.taglist; + const added: number[] = []; + const removed: number[] = []; + if (!this.shapeArgs) return this.Load(); + + if (tl.length) { + const nl = (this.listLength = tl.length); + const ol = taglist.length; + for (let i = 0; i < ol; ++i) { + newlist.push(taglist[i]); + removed.push(i); + } + for (let i = 0; i < nl; ++i) { + let found = 0; + for (let j = 0; j < ol; ++j) { + if (taglist[j].EqualTo(tl[i])) { + this.UpdateTag(newlist[j], tl[i]); + found = 1; + removed[j] = -1; + } + } + if (!found) added.push(i); + } + for (let i = 0, j = 0; i < ol; ++i) { + if (removed[j] === -1) removed.splice(j, 1); + else ++j; + } + if (removed.length) { + Shuffle(removed); + while (removed.length && added.length) { + const i = removed.shift()!; + const j = added.shift()!; + newlist[i] = this.CreateTag(tl[j]); + } + removed.sort((a, b) => a - b); + while (removed.length) newlist.splice(removed.pop()!, 1); + } + let j = newlist.length / (added.length + 1); + let i = 0; + while (added.length) { + newlist.splice(Math.ceil(++i * j), 0, this.CreateTag(tl[added.shift()!])); + } + this.shapeArgs[0] = newlist.length; + const vl = this.shape.apply(this, this.shapeArgs); + for (let k = 0; k < newlist.length; ++k) newlist[k].position = new Vector(vl[k][0], vl[k][1], vl[k][2]); + this.weight && this.Weight(newlist); + } + this.taglist = newlist; + } + + SetShadow(c: CanvasRenderingContext2D) { + c.shadowBlur = this.shadowBlur; + c.shadowOffsetX = this.shadowOffset[0]; + c.shadowOffsetY = this.shadowOffset[1]; + } + + LoadAudio() { + if (!TagCanvas.sharedAudioContext && !SetupAudio()) return; + const audio = TagCanvas.sharedAudioContext; + if (audio === 'off' || !audio) return; + this.audio = document.createElement('audio'); + this.audio.src = this.activeAudio; + this.track = audio.createMediaElementSource(this.audio); + this.gain = audio.createGain(); + this.track.connect(this.gain); + this.gain.connect(audio.destination); + this.hasAudio = 1; + TagCanvas.sharedAudioClick = function () { + const ctx = TagCanvas.sharedAudioContext; + ctx && ctx !== 'off' && (ctx as any).resume(); + TagCanvas.sharedAudioClick && document.removeEventListener('click', TagCanvas.sharedAudioClick); + TagCanvas.sharedAudioClick = undefined; + }; + document.addEventListener('click', TagCanvas.sharedAudioClick); + } + + ShowAudioIcon() { + const s = this.audioIconSize; + const cv = this.canvas; + const c = this.ctxt; + const x = cv.width - s - 3; + const y = cv.height - s - 3; + const t = this.audioIconThickness; + const c1 = '#000'; + const c2 = '#fff'; + const d = this.audioIconDark; + let muted = this.audioOff; + const sus = 'suspended'; + const audio = TagCanvas.sharedAudioContext; + if (!audio || audio === 'off') return; + if (!muted) muted = ((audio as any).state === sus); + if (this.audioIcon && this.hasAudio) { + AudioIcon(muted, c, s, x, y, t + 1, d ? c2 : c1); + AudioIcon(muted, c, s, x, y, t, d ? c1 : c2); + } + } + + CheckAudioIcon() { + const s = this.audioIconSize; + const cv = this.canvas; + const t = this.audioIconThickness / 2; + const x = cv.width - s - 3 - t; + const y = cv.height - s - 3 - t; + if (this.audioIcon && this.mx >= x && this.my >= y) return true; + return false; + } + + ToggleAudio() { + const audio = TagCanvas.sharedAudioContext; + const on = this.audioOff || (audio && audio !== 'off' && (audio as any).state === 'suspended'); + on || (this.currentAudio && this.currentAudio.StopAudio()); + this.audioOff = !on; + } + + Draw(t?: number) { + if (this.paused) return; + const cv = this.canvas; + const cw = cv.width; + const ch = cv.height; + let max_sc = 0; + const now = t ?? TimeNow(); + const tdelta = ((now - this.time) * TagCanvas.interval) / 1000; + const x = cw / 2 + this.offsetX; + const y = ch / 2 + this.offsetY; + const c = this.ctxt; + let active: any; + let a: any; + let aindex = -1; + let tl: any[] = this.taglist; + const l = tl.length; + const last = this.active && this.active.tag; + let cursor = ''; + const frontsel = this.frontSelect; + let centreDrawn = this.centreFunc === Nop; + this.time = now; + if (this.frozen && this.drawn) return this.Animate(cw, ch, tdelta); + const fixed = this.AnimateFixed(); + c.setTransform(1, 0, 0, 1, 0, 0); + for (let i = 0; i < l; ++i) tl[i].Calc(this.transform, this.fixedAlpha); + tl = SortList(tl, (a, b) => b.z - a.z); + + if (fixed && this.fixedAnim.active) { + active = this.fixedAnim.tag.UpdateActive(c, x, y); + } else { + this.active = null; + if (this.CheckAudioIcon()) { + cursor = 'pointer'; + } else { + for (let i = 0; i < l; ++i) { + a = this.mx >= 0 && this.my >= 0 && this.taglist[i].CheckActive(c, x, y); + if (a && a.sc > max_sc && (!frontsel || a.z <= 0)) { + active = a; + aindex = i; + active.tag = this.taglist[i]; + max_sc = a.sc; + } + } + this.active = active; + } + } + + this.txtOpt || (this.shadow && this.SetShadow(c)); + c.clearRect(0, 0, cw, ch); + for (let i = 0; i < l; ++i) { + if (!centreDrawn && tl[i].z <= 0) { + try { + this.centreFunc(c, cw, ch, x, y); + } catch (e) { + alert(e); + this.centreFunc = Nop; + } + centreDrawn = true; + } + if (!(active && active.tag === tl[i] && active.PreDraw(c, tl[i], x, y))) tl[i].Draw(c, x, y); + active && active.tag === tl[i] && active.PostDraw(c); + } + + if (this.freezeActive && active) { + this.Freeze(); + } else { + this.UnFreeze(); + this.drawn = l === this.listLength; + } + + if (this.fixedCallback) { + this.fixedCallback(this, this.fixedCallbackTag); + this.fixedCallback = null; + } + + fixed || this.Animate(cw, ch, tdelta); + + if (active) { + active.LastDraw(c); + if (active.tag !== last) { + this.currentAudio && this.currentAudio !== active.tag && this.currentAudio.StopAudio(); + if (active.tag.PlayAudio()) this.currentAudio = active.tag; + } + cursor = this.activeCursor; + } + + cv.style.cursor = cursor; + this.Tooltip(active, this.taglist[aindex]); + this.audioIcon && this.ShowAudioIcon(); + } + + TooltipNone() {} + + TooltipNative(active: any, tag: any) { + if (active) this.canvas.title = tag && tag.title ? tag.title : ''; + else this.canvas.title = this.ctitle; + } + + SetTTDiv(title: string, tag?: any) { + const tc: any = this; + const s = tc.ttdiv.style; + if (title !== tc.ttdiv.innerHTML) s.display = 'none'; + tc.ttdiv.innerHTML = title; + tag && (tag.title = tc.ttdiv.innerHTML); + if (s.display === 'none' && !tc.tttimer) { + tc.tttimer = setTimeout(function () { + const p = AbsPos(tc.canvas.id); + s.display = 'block'; + s.left = p.x + tc.mx + 'px'; + s.top = p.y + tc.my + 24 + 'px'; + tc.tttimer = null; + }, tc.tooltipDelay); + } + } + + TooltipDiv(active: any, tag: any) { + if (active && tag && tag.title) { + this.SetTTDiv(tag.title, tag); + } else if (!active && this.mx !== -1 && this.my !== -1 && this.ctitle.length) { + this.SetTTDiv(this.ctitle); + } else { + this.ttdiv.style.display = 'none'; + } + } + + Tooltip: any = this.TooltipNone; + + Transform(tc: any, p: number, y: number) { + if (p || y) { + const sp = Math.sin(p); + const cp = Math.cos(p); + const sy = Math.sin(y); + const cy = Math.cos(y); + const ym = new Matrix([cy, 0, sy, 0, 1, 0, -sy, 0, cy]); + const pm = new Matrix([1, 0, 0, 0, cp, -sp, 0, sp, cp]); + tc.transform = tc.transform.mul(ym.mul(pm)); + } + } + + AnimateFixed() { + if (this.fadeIn) { + const t1 = TimeNow() - this.startTime; + if (t1 >= this.fadeIn) { + this.fadeIn = 0; + this.fixedAlpha = 1; + } else { + this.fixedAlpha = t1 / this.fadeIn; + } + } + if (this.fixedAnim) { + if (!this.fixedAnim.transform) this.fixedAnim.transform = this.transform; + const fa = this.fixedAnim; + const t1 = TimeNow() - fa.t0; + let angle = fa.angle; + const d = this.animTiming(fa.t, t1); + this.transform = fa.transform; + if (t1 >= fa.t) { + this.fixedCallbackTag = fa.tag; + this.fixedCallback = fa.cb; + this.fixedAnim = this.yaw = this.pitch = 0; + } else { + angle *= d; + } + const m = Matrix.Rotation(angle, fa.axis); + this.transform = this.transform.mul(m); + return this.fixedAnim !== 0; + } + return false; + } + + AnimatePosition(w: number, h: number, t: number) { + const tc: any = this; + const x = tc.mx; + const y = tc.my; + if (!tc.frozen && x >= 0 && y >= 0 && x < w && y < h) { + const s = tc.maxSpeed; + const r = tc.reverse ? -1 : 1; + tc.lx || (tc.yaw = ((x * 2 * s) / w - s) * r * t); + tc.ly || (tc.pitch = ((y * 2 * s) / h - s) * -r * t); + tc.initial = null; + } else if (!tc.initial) { + if (tc.frozen && !tc.freezeDecel) tc.yaw = tc.pitch = 0; + else tc.Decel(tc); + } + this.Transform(tc, tc.pitch, tc.yaw); + } + + AnimateDrag(w: number, _h: number, t: number) { + const tc: any = this; + const rs = (100 * t * tc.maxSpeed) / tc.max_radius / tc.zoom; + if (tc.dx || tc.dy) { + tc.lx || (tc.yaw = (tc.dx * rs) / tc.stretchX); + tc.ly || (tc.pitch = (tc.dy * -rs) / tc.stretchY); + tc.dx = tc.dy = 0; + tc.initial = null; + } else if (!tc.initial) { + tc.Decel(tc); + } + this.Transform(tc, tc.pitch, tc.yaw); + } + + Freeze() { + if (!this.frozen) { + this.preFreeze = [this.yaw, this.pitch]; + this.frozen = 1; + this.drawn = 0; + } + } + + UnFreeze() { + if (this.frozen) { + this.yaw = this.preFreeze[0]; + this.pitch = this.preFreeze[1]; + this.frozen = 0; + } + } + + Decel(tc: any) { + const s = tc.minSpeed; + const ay = Math.abs(tc.yaw); + const ap = Math.abs(tc.pitch); + if (!tc.lx && ay > s) tc.yaw = ay > tc.z0 ? tc.yaw * tc.decel : 0; + if (!tc.ly && ap > s) tc.pitch = ap > tc.z0 ? tc.pitch * tc.decel : 0; + } + + Zoom(r: number) { + this.z2 = this.z1 * (1 / r); + this.drawn = 0; + } + + Clicked(e?: any) { + if (this.CheckAudioIcon()) { + this.ToggleAudio(); + return; + } + const a = this.active; + try { + if (a && a.tag) + if (this.clickToFront === false || this.clickToFront === null) a.tag.Clicked(e); + else this.TagToFront(a.tag, this.clickToFront, function () { a.tag.Clicked(e); }, true); + } catch {} + } + + Wheel(i: any) { + const z = this.zoom + this.zoomStep * (i ? 1 : -1); + this.zoom = Math.min(this.zoomMax, Math.max(this.zoomMin, z)); + this.Zoom(this.zoom); + } + + BeginDrag(e: any) { + this.down = EventXY(e, this.canvas); + e.cancelBubble = true; + e.returnValue = false; + e.preventDefault && e.preventDefault(); + } + + Drag(e: any, p: any) { + if (this.dragControl && this.down) { + const t2 = this.dragThreshold * this.dragThreshold; + const dx = p.x - this.down.x; + const dy = p.y - this.down.y; + if (this.dragging || dx * dx + dy * dy > t2) { + this.dx = dx; + this.dy = dy; + this.dragging = 1; + this.down = p; + } + } + return this.dragging; + } + + EndDrag() { + const res = this.dragging; + this.dragging = this.down = null; + return res; + } + + BeginPinch(e: any) { + this.pinched = [PinchDistance(e), this.zoom]; + e.preventDefault && e.preventDefault(); + } + + Pinch(e: any) { + const p = this.pinched; + if (!p) return; + const d = PinchDistance(e); + const z = (p[1] * d) / p[0]; + this.zoom = Math.min(this.zoomMax, Math.max(this.zoomMin, z)); + this.Zoom(this.zoom); + } + + EndPinch(_e?: any) { + this.pinched = null; + } + + Pause() { + this.paused = true; + } + + Resume() { + this.paused = false; + } + + SetSpeed(i: [number, number]) { + this.initial = i; + this.yaw = i[0] * this.maxSpeed; + this.pitch = i[1] * this.maxSpeed; + } + + FindTag(t: any) { + if (!Defined(t)) return null; + Defined(t.index) && (t = t.index); + if (!IsObject(t)) return this.taglist[t]; + let srch: any; + let tgt: any; + if (Defined((t as any).id)) { + srch = 'id'; + tgt = (t as any).id; + } else if (Defined((t as any).text)) { + srch = 'innerText'; + tgt = (t as any).text; + } + for (let i = 0; i < this.taglist.length; ++i) if (this.taglist[i].a[srch] === tgt) return this.taglist[i]; + } + + RotateTag(tag: any, lt: number, lg: number, time: number, callback?: any, active?: any) { + const t = tag.Calc(this.transform, 1); + const v1 = new Vector(t.x, t.y, t.z); + const v2 = MakeVector(lg, lt); + const angle = v1.angle(v2); + const u = v1.cross(v2).unit(); + if (angle === 0) { + this.fixedCallbackTag = tag; + this.fixedCallback = callback; + } else { + this.fixedAnim = { + angle: -angle, + axis: u, + t: time, + t0: TimeNow(), + cb: callback, + tag: tag, + active: active, + }; + } + } + + TagToFront(tag: any, time: number, callback?: any, active?: any) { + this.RotateTag(tag, 0, 0, time, callback, active); + } + + Volume(vol: number) { + this.audioVolume = vol * 1; + } + + static Start(id: string, l?: string, o?: TagCanvasOptions) { + TagCanvas.Delete(id); + TagCanvas.tc[id] = new TagCanvas(id, l, o); + } + + static Linear(t: number, t0: number) { + return t0 / t; + } + static Smooth(t: number, t0: number) { + return 0.5 - Math.cos((t0 * Math.PI) / t) / 2; + } + + static Pause(id: string) { + TagCanvas.tc[id] && TagCanvas.tc[id].Pause(); + } + static Resume(id: string) { + TagCanvas.tc[id] && TagCanvas.tc[id].Resume(); + } + static Reload(id: string) { + TagCanvas.tc[id] && TagCanvas.tc[id].Load(); + } + static UpdateCanvas(id: string) { + TagCanvas.tc[id] && TagCanvas.tc[id].Update(); + } + + static SetSpeedStatic(id: string, speed: any) { + if (IsObject(speed) && TagCanvas.tc[id] && !Number.isNaN(speed[0]) && !Number.isNaN(speed[1])) { + TagCanvas.tc[id].SetSpeed(speed); + return true; + } + return false; + } + + static TagToFrontStatic(id: string, options: any) { + if (!IsObject(options)) return false; + options.lat = options.lng = 0; + return TagCanvas.RotateTagStatic(id, options); + } + + static RotateTagStatic(id: string, options: any) { + if (IsObject(options) && TagCanvas.tc[id]) { + if (Number.isNaN(options.time)) options.time = 500; + const tt = TagCanvas.tc[id].FindTag(options); + if (tt) { + TagCanvas.tc[id].RotateTag(tt, options.lat, options.lng, options.time, options.callback, options.active); + return true; + } + } + return false; + } + + static Delete(id: string) { + if (TagCanvas.handlers[id]) { + const c = document.getElementById(id); + if (c) { + for (let i = 0; i < TagCanvas.handlers[id].length; ++i) + RemoveHandler(TagCanvas.handlers[id][i][0], TagCanvas.handlers[id][i][1], c); + } + } + delete TagCanvas.handlers[id]; + delete TagCanvas.tc[id]; + } +} + +function PinchDistance(e: any) { + const t1 = e.targetTouches[0]; + const t2 = e.targetTouches[1]; + return Math.sqrt(Math.pow(t2.pageX - t1.pageX, 2) + Math.pow(t2.pageY - t1.pageY, 2)); +} \ No newline at end of file diff --git a/src/tagcanvas/geometry.ts b/src/tagcanvas/geometry.ts new file mode 100644 index 0000000..60820bb --- /dev/null +++ b/src/tagcanvas/geometry.ts @@ -0,0 +1,124 @@ +import type { XYZ } from '@/tagcanvas/types'; + +export class Vector { + constructor( + public x: number, + public y: number, + public z: number, + ) {} + + length() { + return Math.sqrt(this.x * this.x + this.y * this.y + this.z * this.z); + } + + dot(v: Vector) { + return this.x * v.x + this.y * v.y + this.z * v.z; + } + + cross(v: Vector) { + const x = this.y * v.z - this.z * v.y; + const y = this.z * v.x - this.x * v.z; + const z = this.x * v.y - this.y * v.x; + return new Vector(x, y, z); + } + + angle(v: Vector) { + const dotv = this.dot(v); + if (dotv === 0) return Math.PI / 2; + const ac = dotv / (this.length() * v.length()); + if (ac >= 1) return 0; + if (ac <= -1) return Math.PI; + return Math.acos(ac); + } + + unit() { + const l = this.length(); + return new Vector(this.x / l, this.y / l, this.z / l); + } +} + +export function MakeVector(lg: number, lt: number) { + lt = (lt * Math.PI) / 180; + lg = (lg * Math.PI) / 180; + const x = Math.sin(lg) * Math.cos(lt); + const y = -Math.sin(lt); + const z = -Math.cos(lg) * Math.cos(lt); + return new Vector(x, y, z); +} + +export class Matrix { + // row-major 3x3 + private m: [[number, number, number], [number, number, number], [number, number, number]]; + + constructor(a: number[]) { + this.m = [ + [a[0], a[1], a[2]], + [a[3], a[4], a[5]], + [a[6], a[7], a[8]], + ]; + } + + static Identity() { + return new Matrix([1, 0, 0, 0, 1, 0, 0, 0, 1]); + } + + static Rotation(angle: number, u: Vector) { + const sina = Math.sin(angle); + const cosa = Math.cos(angle); + const mcos = 1 - cosa; + return new Matrix([ + cosa + Math.pow(u.x, 2) * mcos, + u.x * u.y * mcos - u.z * sina, + u.x * u.z * mcos + u.y * sina, + u.y * u.x * mcos + u.z * sina, + cosa + Math.pow(u.y, 2) * mcos, + u.y * u.z * mcos - u.x * sina, + u.z * u.x * mcos - u.y * sina, + u.z * u.y * mcos + u.x * sina, + cosa + Math.pow(u.z, 2) * mcos, + ]); + } + + mul(m: Matrix | number) { + const a: number[] = []; + if (typeof m === 'number') { + for (let i = 0; i < 3; ++i) { + for (let j = 0; j < 3; ++j) { + a.push(this.m[i][j] * m); + } + } + return new Matrix(a); + } + for (let i = 0; i < 3; ++i) { + for (let j = 0; j < 3; ++j) { + a.push( + this.m[i][0] * m.m[0][j] + + this.m[i][1] * m.m[1][j] + + this.m[i][2] * m.m[2][j], + ); + } + } + return new Matrix(a); + } + + xform(p: Vector): XYZ { + const x = p.x, + y = p.y, + z = p.z; + return { + x: x * this.m[0][0] + y * this.m[1][0] + z * this.m[2][0], + y: x * this.m[0][1] + y * this.m[1][1] + z * this.m[2][1], + z: x * this.m[0][2] + y * this.m[1][2] + z * this.m[2][2], + }; + } +} + +export function Project(tc: any, p1: any, sx: number, sy: number) { + const m = (tc.radius * tc.z1) / (tc.z1 + tc.z2 + p1.z); + return { + x: p1.x * m * sx, + y: p1.y * m * sy, + z: p1.z, + w: (tc.z1 - p1.z) / tc.z2, + }; +} diff --git a/src/tagcanvas/options.ts b/src/tagcanvas/options.ts new file mode 100644 index 0000000..867e1ed --- /dev/null +++ b/src/tagcanvas/options.ts @@ -0,0 +1,105 @@ +import { Nop } from '@/tagcanvas/util'; +import type { TagCanvasOptions } from '@/tagcanvas/types'; + +export const defaultTagCanvasOptions: Required = { + z1: 20000, + z2: 20000, + z0: 0.0002, + freezeActive: false, + freezeDecel: false, + activeCursor: 'pointer', + pulsateTo: 1, + pulsateTime: 3, + reverse: false, + depth: 0.5, + maxSpeed: 0.05, + minSpeed: 0, + decel: 0.95, + interval: 20, + minBrightness: 0.1, + maxBrightness: 1, + outlineColour: '#ffff99', + outlineThickness: 2, + outlineOffset: 5, + outlineMethod: 'outline', + outlineRadius: 0, + textColour: '#ff99ff', + textHeight: 15, + textFont: 'Helvetica, Arial, sans-serif', + shadow: '#000', + shadowBlur: 0, + shadowOffset: [0, 0], + initial: null, + hideTags: true, + zoom: 1, + weight: false, + weightMode: 'size', + weightFrom: null, + weightSize: 1, + weightSizeMin: null, + weightSizeMax: null, + weightGradient: { 0: '#f00', 0.33: '#ff0', 0.66: '#0f0', 1: '#00f' }, + txtOpt: true, + txtScale: 2, + frontSelect: false, + wheelZoom: true, + zoomMin: 0.3, + zoomMax: 3, + zoomStep: 0.05, + shape: 'sphere', + lock: null, + tooltip: null, + tooltipDelay: 300, + tooltipClass: 'tctooltip', + radiusX: 1, + radiusY: 1, + radiusZ: 1, + stretchX: 1, + stretchY: 1, + offsetX: 0, + offsetY: 0, + shuffleTags: false, + noSelect: false, + noMouse: false, + imageScale: 1, + paused: false, + dragControl: false, + dragThreshold: 4, + centreFunc: Nop as any, + splitWidth: 0, + animTiming: 'Smooth', + clickToFront: false, + fadeIn: 0, + padding: 0, + bgColour: null, + bgRadius: 0, + bgOutline: null, + bgOutlineThickness: 0, + outlineIncrease: 4, + textAlign: 'centre', + textVAlign: 'middle', + imageMode: null, + imagePosition: null, + imagePadding: 2, + imageAlign: 'centre', + imageVAlign: 'middle', + noTagsMessage: true, + centreImage: null, + pinchZoom: false, + repeatTags: 0, + minTags: 0, + imageRadius: 0, + scrollPause: false, + outlineDash: 0, + outlineDashSpace: 0, + outlineDashSpeed: 1, + activeAudio: '', + audioVolume: 1, + audioIcon: 1, + audioIconSize: 20, + audioIconThickness: 2, + audioIconDark: 0, + altImage: 0, +}; + +export type { TagCanvasOptions }; diff --git a/src/tagcanvas/shapes.ts b/src/tagcanvas/shapes.ts new file mode 100644 index 0000000..55762fd --- /dev/null +++ b/src/tagcanvas/shapes.ts @@ -0,0 +1,58 @@ +import type { ThreeDCoord } from '@/tagcanvas/types'; + +export function PointsOnSphere(n: number, xr: number, yr: number, zr: number, magic?: any): ThreeDCoord[] { + const pts: ThreeDCoord[] = []; + const off = 2 / n; + const inc = Math.PI * (3 - Math.sqrt(5) + (parseFloat(magic) ? parseFloat(magic) : 0)); + for (let i = 0; i < n; ++i) { + const y = i * off - 1 + off / 2; + const r = Math.sqrt(1 - y * y); + const phi = i * inc; + pts.push([Math.cos(phi) * r * xr, y * yr, Math.sin(phi) * r * zr]); + } + return pts; +} + +function Cylinder(n: number, o: number, xr: number, yr: number, zr: number, magic?: any): ThreeDCoord[] { + const pts: ThreeDCoord[] = []; + const off = 2 / n; + const inc = Math.PI * (3 - Math.sqrt(5) + (parseFloat(magic) ? parseFloat(magic) : 0)); + for (let i = 0; i < n; ++i) { + const j = i * off - 1 + off / 2; + const phi = i * inc; + const k = Math.cos(phi); + const l = Math.sin(phi); + pts.push(o ? [j * xr, k * yr, l * zr] : [k * xr, j * yr, l * zr]); + } + return pts; +} + +function Ring(o: number, n: number, xr: number, yr: number, zr: number, j: number): ThreeDCoord[] { + const pts: ThreeDCoord[] = []; + const inc = (Math.PI * 2) / n; + for (let i = 0; i < n; ++i) { + const phi = i * inc; + const k = Math.cos(phi); + const l = Math.sin(phi); + pts.push(o ? [j * xr, k * yr, l * zr] : [k * xr, j * yr, l * zr]); + } + return pts; +} + +export function PointsOnCylinderV(n: number, xr: number, yr: number, zr: number, m?: any) { + return Cylinder(n, 0, xr, yr, zr, m); +} + +export function PointsOnCylinderH(n: number, xr: number, yr: number, zr: number, m?: any) { + return Cylinder(n, 1, xr, yr, zr, m); +} + +export function PointsOnRingV(n: number, xr: number, yr: number, zr: number, offset?: any) { + offset = Number.isNaN(offset) ? 0 : offset * 1; + return Ring(0, n, xr, yr, zr, offset); +} + +export function PointsOnRingH(n: number, xr: number, yr: number, zr: number, offset?: any) { + offset = Number.isNaN(offset) ? 0 : offset * 1; + return Ring(1, n, xr, yr, zr, offset); +} diff --git a/src/tagcanvas/types.ts b/src/tagcanvas/types.ts new file mode 100644 index 0000000..039e775 --- /dev/null +++ b/src/tagcanvas/types.ts @@ -0,0 +1,110 @@ +export interface TagCanvasOptions { + z1?: number; + z2?: number; + z0?: number; + freezeActive?: boolean; + freezeDecel?: boolean; + activeCursor?: string; + pulsateTo?: number; + pulsateTime?: number; + reverse?: boolean; + depth?: number; + maxSpeed?: number; + minSpeed?: number; + decel?: number; + interval?: number; + minBrightness?: number; + maxBrightness?: number; + outlineColour?: string; + outlineThickness?: number; + outlineOffset?: number; + outlineMethod?: string; + outlineRadius?: number; + textColour?: string; + textHeight?: number; + textFont?: string; + shadow?: string; + shadowBlur?: number; + shadowOffset?: [number, number]; + initial?: [number, number] | null; + hideTags?: boolean; + zoom?: number; + weight?: boolean; + weightMode?: string; + weightFrom?: string | null; + weightSize?: number; + weightSizeMin?: number | null; + weightSizeMax?: number | null; + weightGradient?: any; + txtOpt?: boolean; + txtScale?: number; + frontSelect?: boolean; + wheelZoom?: boolean; + zoomMin?: number; + zoomMax?: number; + zoomStep?: number; + shape?: any; + lock?: any; + tooltip?: any; + tooltipDelay?: number; + tooltipClass?: string; + radiusX?: number; + radiusY?: number; + radiusZ?: number; + stretchX?: number; + stretchY?: number; + offsetX?: number; + offsetY?: number; + shuffleTags?: boolean; + noSelect?: boolean; + noMouse?: boolean; + imageScale?: number; + paused?: boolean; + dragControl?: boolean; + dragThreshold?: number; + centreFunc?: ( + c: CanvasRenderingContext2D, + w: number, + h: number, + cx: number, + cy: number, + ) => void; + splitWidth?: number; + animTiming?: string | ((t: number, t0: number) => number); + clickToFront?: any; + fadeIn?: number; + padding?: number; + bgColour?: string | null; + bgRadius?: number; + bgOutline?: string | null; + bgOutlineThickness?: number; + outlineIncrease?: number; + textAlign?: string; + textVAlign?: string; + imageMode?: string | null; + imagePosition?: string | null; + imagePadding?: number; + imageAlign?: string; + imageVAlign?: string; + noTagsMessage?: boolean; + centreImage?: string | null; + pinchZoom?: boolean; + repeatTags?: number; + minTags?: number; + imageRadius?: any; + scrollPause?: any; + outlineDash?: number; + outlineDashSpace?: number; + outlineDashSpeed?: number; + activeAudio?: any; + audioVolume?: number; + audioIcon?: any; + audioIconSize?: number; + audioIconThickness?: number; + audioIconDark?: any; + altImage?: any; +} + +export type ThreeDCoord = [x: number, y: number, z: number]; +export type XY = { x: number; y: number }; +export type XYZ = { x: number; y: number; z: number }; diff --git a/src/tagcanvas/util.ts b/src/tagcanvas/util.ts new file mode 100644 index 0000000..b2d49cc --- /dev/null +++ b/src/tagcanvas/util.ts @@ -0,0 +1,36 @@ +export function Defined(d: T): d is Exclude { + return typeof d !== 'undefined'; +} + +export function IsObject(o: unknown): o is Record { + return typeof o === 'object' && o !== null; +} + +export function Clamp(v: number, mn: number, mx: number) { + return Number.isNaN(v) ? mx : Math.min(mx, Math.max(mn, v)); +} + +export function Nop() { + return false; +} + +export function TimeNow() { + return Date.now(); +} + +export function SortList(l: T[], f: (a: T, b: T) => number) { + const nl = l.slice(); + nl.sort(f); + return nl; +} + +export function Shuffle(a: T[]) { + let i = a.length - 1; + while (i) { + const p = ~~(Math.random() * i); + const t = a[i]; + a[i] = a[p]; + a[p] = t; + --i; + } +} diff --git a/tagcanvas.jquery.json b/tagcanvas.jquery.json deleted file mode 100644 index 55f1b49..0000000 --- a/tagcanvas.jquery.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "tagcanvas", - "version": "2.11.1", - "title": "TagCanvas HTML5 canvas tag cloud", - "description": "Displays tags as a 3D rotating tag cloud using an HTML5 canvas", - "keywords": [ "html5", "canvas", "animation", "tag", "cloud" ], - "homepage": "http://www.goat1000.com/tagcanvas.php", - "docs": "http://www.goat1000.com/tagcanvas-install.php", - "download": "http://www.goat1000.com/tagcanvas.php#links", - "author": { - "name": "Graham Breach", - "email": "graham@goat1000.com" - }, - "licenses": [ - { - "type": "LGPLv3", - "url": "http://www.gnu.org/licenses/lgpl-3.0.html" - } - ], - "dependencies": { - "jquery": ">=1.1" - } -} diff --git a/tagcanvas.min.js b/tagcanvas.min.js deleted file mode 100644 index 61b9384..0000000 --- a/tagcanvas.min.js +++ /dev/null @@ -1,21 +0,0 @@ -/** - * Copyright (C) 2010-2021 Graham Breach - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program. If not, see . - */ -/** - * TagCanvas 2.11 - * For more information, please contact - */ -(function(){"use strict";var r,C,p=Math.abs,o=Math.sin,l=Math.cos,g=Math.max,h=Math.min,af=Math.ceil,E=Math.sqrt,w=Math.pow,I={},D={},R={0:"0,",1:"17,",2:"34,",3:"51,",4:"68,",5:"85,",6:"102,",7:"119,",8:"136,",9:"153,",a:"170,",A:"170,",b:"187,",B:"187,",c:"204,",C:"204,",d:"221,",D:"221,",e:"238,",E:"238,",f:"255,",F:"255,"},f,d,b,T,z,F,M,c=document,v,e,P,j={};for(r=0;r<256;++r)C=r.toString(16),r<16&&(C='0'+C),D[C]=D[C.toUpperCase()]=r.toString()+',';function n(a){return typeof a!='undefined'}function B(a){return typeof a=='object'&&a!=null}function G(a,c,b){return isNaN(a)?b:h(b,g(c,a))}function x(){return!1}function q(){return(new Date).valueOf()}function ak(c,d){var b=[],e=c.length,a;for(a=0;a=1)?0:a<=-1?Math.PI:Math.acos(a)},z.unit=function(){var a=this.length();return new s(this.x/a,this.y/a,this.z/a)};function ay(b,a){a=a*Math.PI/180,b=b*Math.PI/180;var c=o(b)*l(a),d=-o(a),e=-l(b)*l(a);return new s(c,d,e)}function m(a){this[1]={1:a[0],2:a[1],3:a[2]},this[2]={1:a[3],2:a[4],3:a[5]},this[3]={1:a[6],2:a[7],3:a[8]}}T=m.prototype,m.Identity=function(){return new m([1,0,0,0,1,0,0,0,1])},m.Rotation=function(e,a){var c=o(e),d=l(e),b=1-d;return new m([d+w(a.x,2)*b,a.x*a.y*b-a.z*c,a.x*a.z*b+a.y*c,a.y*a.x*b+a.z*c,d+w(a.y,2)*b,a.y*a.z*b-a.x*c,a.z*a.x*b-a.y*c,a.z*a.y*b+a.x*c,d+w(a.z,2)*b])},T.mul=function(c){var d=[],a,b,e=c.xform?1:0;for(a=1;a<=3;++a)for(b=1;b<=3;++b)e?d.push(this[a][1]*c[1][b]+this[a][2]*c[2][b]+this[a][3]*c[3][b]):d.push(this[a][b]*c);return new m(d)},T.xform=function(b){var a={},c=b.x,d=b.y,e=b.z;return a.x=c*this[1][1]+d*this[2][1]+e*this[3][1],a.y=c*this[1][2]+d*this[2][2]+e*this[3][2],a.z=c*this[1][3]+d*this[2][3]+e*this[3][3],a};function aB(g,j,k,m,f){var a,b,c,d,e=[],h=2/g,i;i=Math.PI*(3-E(5)+(parseFloat(f)?parseFloat(f):0));for(a=0;a0)}function aC(a,c,f,d){var e=a.createLinearGradient(0,0,c,0),b;for(b in d)e.addColorStop(1-b,d[b]);a.fillStyle=e,a.fillRect(0,f,c,1)}function L(a,m,j){var l=1024,d=1,e=a.weightGradient,i,f,b,c;if(a.gCanvas)f=a.gCanvas.getContext('2d'),d=a.gCanvas.height;else{if(B(e[0])?d=e.length:e=[e],a.gCanvas=i=k(l,d),!i)return null;f=i.getContext('2d');for(b=0;b0?b=i*b/100:b=b*j,a=e.getContext('2d'),a.globalCompositeOperation='source-over',a.fillStyle='#fff',b>=i/2?(b=h(c,d)/2,a.beginPath(),a.moveTo(c/2,d/2),a.arc(c/2,d/2,b,0,2*Math.PI,!1),a.fill(),a.closePath()):(b=h(c/2,d/2,b),y(a,0,0,c,d,b,!0),a.fill()),a.globalCompositeOperation='source-in',a.drawImage(l,0,0,c,d),e)}function ao(q,m,i,b,h,a,c){var g=p(c[0]),f=p(c[1]),j=m+(g>a?g+a:a*2)*b,l=i+(f>a?f+a:a*2)*b,n=b*((a||0)+(c[0]<0?g:0)),o=b*((a||0)+(c[1]<0?f:0)),e,d;return e=k(j,l),!e?null:(d=e.getContext('2d'),h&&(d.shadowColor=h),a&&(d.shadowBlur=a*b),c&&(d.shadowOffsetX=c[0]*b,d.shadowOffsetY=c[1]*b),d.drawImage(q,n,o,m,i),{image:e,width:j/b,height:l/b})}function ae(m,o,l){var c=parseInt(m.toString().length*l),h=parseInt(l*2*m.length),j=k(c,h),g,i,e,f,b,d,n,a;if(!j)return null;g=j.getContext('2d'),g.fillStyle='#000',g.fillRect(0,0,c,h),Y(g,l+'px '+o,'#fff',m,0,0,0,0,[],'centre'),i=g.getImageData(0,0,c,h),e=i.width,f=i.height,a={min:{x:e,y:f},max:{x:-1,y:-1}};for(d=0;d0&&(ba.max.x&&(a.max.x=b),da.max.y&&(a.max.y=d));return e!=c&&(a.min.x*=c/e,a.max.x*=c/e),f!=h&&(a.min.y*=c/f,a.max.y*=c/f),j=null,a}function Q(a){return"'"+a.replace(/(\'|\")/g,'').replace(/\s*,\s*/g,"', '")+"'"}function t(b,d,a){a=a||c,a.addEventListener?a.addEventListener(b,d,!1):a.attachEvent('on'+b,d)}function am(b,d,a){a=a||c,a.removeEventListener?a.removeEventListener(b,d):a.detachEvent('on'+b,d)}function A(g,e,j,a,b){var l=b.imageScale,h,c,k,m,f,d;if(!e.complete)return t('load',function(){A(g,e,j,a,b)},e);if(!g.complete)return t('load',function(){A(g,e,j,a,b)},g);if(j&&!j.complete)return t('load',function(){A(g,e,j,a,b)},j);e.width=e.width,e.height=e.height,l&&(g.width=e.width*l,g.height=e.height*l),a.iw=g.width,a.ih=g.height,b.txtOpt&&(c=g,h=b.zoomMax*b.txtScale,f=a.iw*h,d=a.ih*h,f0?(a.iw+=2*b.outlineIncrease,a.ih+=2*b.outlineIncrease,f=h*a.iw,d=h*a.ih,c=S(a.fimage,f,d),a.oimage=c,a.fimage=H(a.fimage,a.oimage.width,a.oimage.height)):(f=h*(a.iw+2*b.outlineIncrease),d=h*(a.ih+2*b.outlineIncrease),c=S(a.fimage,f,d),a.oimage=H(c,a.fimage.width,a.fimage.height))))),a.alt=j,a.Init()}function i(a,d){var b=c.defaultView,e=d.replace(/\-([a-z])/g,function(a){return a.charAt(1).toUpperCase()});return b&&b.getComputedStyle&&b.getComputedStyle(a,null).getPropertyValue(d)||a.currentStyle&&a.currentStyle[e]}function aj(c,d,e){var b=1,a;return d?b=1*(c.getAttribute(d)||e):(a=i(c,'font-size'))&&(b=a.indexOf('px')>-1&&a.replace('px','')*1||a.indexOf('pt')>-1&&a.replace('pt','')*1.25||a*3.3),b}function u(a){return a.target&&n(a.target.id)?a.target.id:a.srcElement.parentNode.id}function K(a,c){var b,d,e=parseInt(i(c,'width'))/c.width,f=parseInt(i(c,'height'))/c.height;return n(a.offsetX)?b={x:a.offsetX,y:a.offsetY}:(d=X(c.id),n(a.changedTouches)&&(a=a.changedTouches[0]),a.pageX&&(b={x:a.pageX-d.x,y:a.pageY-d.y})),b&&e&&f&&(b.x/=e,b.y/=f),b}function an(c){var d=c.target||c.fromElement.parentNode,b=a.tc[d.id];b&&(b.mx=b.my=-1,b.UnFreeze(),b.EndDrag())}function ad(e){var g,c=a,b,d,f=u(e);for(g in c.tc)b=c.tc[g],b.tttimer&&(clearTimeout(b.tttimer),b.tttimer=null);f&&c.tc[f]&&(b=c.tc[f],(d=K(e,b.canvas))&&(b.mx=d.x,b.my=d.y,b.Drag(e,d)),b.drawn=0)}function ap(b){var e=a,f=c.addEventListener?0:1,d=u(b);d&&b.button==f&&e.tc[d]&&e.tc[d].BeginDrag(b)}function aq(b){var f=a,g=c.addEventListener?0:1,e=u(b),d;e&&b.button==g&&f.tc[e]&&(d=f.tc[e],ad(b),!d.EndDrag()&&!d.touchState&&d.Clicked(b))}function ar(c){var e=u(c),b=e&&a.tc[e],d;b&&c.changedTouches&&(c.touches.length==1&&b.touchState==0?(b.touchState=1,b.BeginDrag(c),(d=K(c,b.canvas))&&(b.mx=d.x,b.my=d.y,b.drawn=0)):c.targetTouches.length==2&&b.pinchZoom?(b.touchState=3,b.EndDrag(),b.BeginPinch(c)):(b.EndDrag(),b.EndPinch(),b.touchState=0))}function ac(c){var d=u(c),b=d&&a.tc[d];if(b&&c.changedTouches){switch(b.touchState){case 1:b.Draw(),b.Clicked();break;break;case 2:b.EndDrag();break;case 3:b.EndPinch()}b.touchState=0}}function au(c){var f,e=a,b,d,g=u(c);for(f in e.tc)b=e.tc[f],b.tttimer&&(clearTimeout(b.tttimer),b.tttimer=null);if(b=g&&e.tc[g],b&&c.changedTouches&&b.touchState){switch(b.touchState){case 1:case 2:(d=K(c,b.canvas))&&(b.mx=d.x,b.my=d.y,b.Drag(c,d)&&(b.touchState=2));break;case 3:b.Pinch(c)}b.drawn=0}}function ab(b){var d=a,c=u(b);c&&d.tc[c]&&(b.cancelBubble=!0,b.returnValue=!1,b.preventDefault&&b.preventDefault(),d.tc[c].Wheel((b.wheelDelta||b.detail)>0))}function aw(d){var c,b=a;clearTimeout(b.scrollTimer);for(c in b.tc)b.tc[c].Pause();b.scrollTimer=setTimeout(function(){var b,c=a;for(b in c.tc)c.tc[b].Resume()},b.scrollPause)}function al(){Z(q())}function Z(b){var c=a.tc,d;a.NextFrame(a.interval),b=b||q();for(d in c)c[d].Draw(b)}function az(){requestAnimationFrame(Z)}function aA(a){setTimeout(al,a)}function X(f){var g=c.getElementById(f),b=g.getBoundingClientRect(),a=c.documentElement,d=c.body,e=window,h=e.pageXOffset||a.scrollLeft,i=e.pageYOffset||a.scrollTop,j=a.clientLeft||d.clientLeft,k=a.clientTop||d.clientTop;return{x:b.left+h-j,y:b.top+i-k}}function aI(a,b,d,e){var c=a.radius*a.z1/(a.z1+a.z2+b.z);return{x:b.x*c*d,y:b.y*c*e,z:b.z,w:(a.z1-b.z)/a.z2}}function V(a){this.e=a,this.br=0,this.line=[],this.text=[],this.original=a.innerText||a.textContent}F=V.prototype,F.Empty=function(){for(var a=0;ah?(d.push(this.line.join(' ')),this.line=[a[b]]):this.line.push(a[b]);d.push(this.line.join(' '))}return this.text=d};function _(a,b){this.ts=null,this.tc=a,this.tag=b,this.x=this.y=this.w=this.h=this.sc=1,this.z=0,this.pulse=1,this.pulsate=a.pulsateTo<1,this.colour=a.outlineColour,this.adash=~~a.outlineDash,this.agap=~~a.outlineDashSpace||this.adash,this.aspeed=a.outlineDashSpeed*1,this.colour=='tag'?this.colour=i(b.a,'color'):this.colour=='tagbg'&&(this.colour=i(b.a,'background-color')),this.Draw=this.pulsate?this.DrawPulsate:this.DrawSimple,this.radius=a.outlineRadius|0,this.SetMethod(a.outlineMethod,a.altImage)}f=_.prototype,f.SetMethod=function(a,d){var b={block:['PreDraw','DrawBlock'],colour:['PreDraw','DrawColour'],outline:['PostDraw','DrawOutline'],classic:['LastDraw','DrawOutline'],size:['PreDraw','DrawSize'],none:['LastDraw']},c=b[a]||b.outline;a=='none'?this.Draw=function(){return 1}:this.drawFunc=this[c[1]],this[c[0]]=this.Draw,d&&(this.RealPreDraw=this.PreDraw,this.PreDraw=this.DrawAlt)},f.Update=function(d,e,i,j,a,f,g,h){var b=this.tc.outlineOffset,c=2*b;this.x=a*d+g-b,this.y=a*e+h-b,this.w=a*i+c,this.h=a*j+c,this.sc=a,this.z=f},f.Ants=function(k){if(!this.adash)return;var b=this.adash,c=this.agap,a=this.aspeed,j=b+c,h=0,g=b,f=c,i=0,d=0,e;a&&(d=p(a)*(q()-this.ts)/50,a<0&&(d=864e4-d),a=~~d%j),a?(b>=a?(h=b-a,g=a):(f=j-a,i=c-f),e=[h,f,g,i]):e=[b,c],k.setLineDash(e)},f.DrawOutline=function(a,d,e,b,c,f){var g=h(this.radius,c/2,b/2);a.strokeStyle=f,this.Ants(a),y(a,d,e,b,c,g,!0)},f.DrawSize=function(i,n,m,l,k,j,a,h,g){var f=a.w,e=a.h,c,b,d;return this.pulsate?(a.image?d=(a.image.height+this.tc.outlineIncrease)/a.image.height:d=a.oscale,b=a.fimage||a.image,c=1+(d-1)*(1-this.pulse),a.h*=c,a.w*=c):b=a.oimage,a.alpha=1,a.Draw(i,h,g,b),a.h=e,a.w=f,1},f.DrawColour=function(d,h,i,e,f,g,a,b,c){return a.oimage?(this.pulse<1?(a.alpha=1-w(this.pulse,2),a.Draw(d,b,c,a.fimage),a.alpha=this.pulse):a.alpha=1,a.Draw(d,b,c,a.oimage),1):this[a.image?'DrawColourImage':'DrawColourText'](d,h,i,e,f,g,a,b,c)},f.DrawColourText=function(f,h,i,j,g,e,a,b,c){var d=a.colour;return a.colour=e,a.alpha=1,a.Draw(f,b,c),a.colour=d,1},f.DrawColourImage=function(a,q,p,o,n,m,i,r,l){var f=a.canvas,e=~~g(q,0),d=~~g(p,0),c=h(f.width-e,o)+.5|0,b=h(f.height-d,n)+.5|0,j;return v?(v.width=c,v.height=b):v=k(c,b),!v?this.SetMethod('outline'):(j=v.getContext('2d'),j.drawImage(f,e,d,c,b,0,0,c,b),a.clearRect(e,d,c,b),this.pulsate?i.alpha=1-w(this.pulse,2):i.alpha=1,i.Draw(a,r,l),a.setTransform(1,0,0,1,0,0),a.save(),a.beginPath(),a.rect(e,d,c,b),a.clip(),a.globalCompositeOperation='source-in',a.fillStyle=m,a.fillRect(e,d,c,b),a.restore(),a.globalAlpha=1,a.globalCompositeOperation='destination-over',a.drawImage(v,0,0,c,b,e,d,c,b),a.globalCompositeOperation='source-over',1)},f.DrawAlt=function(b,a,c,d,f,g){var e=this.RealPreDraw(b,a,c,d,f,g);return a.alt&&(a.DrawImage(b,c,d,a.alt),e=1),e},f.DrawBlock=function(a,d,e,b,c,f){var g=h(this.radius,c/2,b/2);a.fillStyle=f,y(a,d,e,b,c,g)},f.DrawSimple=function(a,b,c,d,e,f){var g=this.tc;return a.setTransform(1,0,0,1,0,0),a.strokeStyle=this.colour,a.lineWidth=g.outlineThickness,a.shadowBlur=a.shadowOffsetX=a.shadowOffsetY=0,a.globalAlpha=f?e:1,this.drawFunc(a,this.x,this.y,this.w,this.h,this.colour,b,c,d)},f.DrawPulsate=function(h,d,e,f){var g=q()-this.ts,c=this.tc,b=c.pulsateTo+(1-c.pulsateTo)*(.5+l(2*Math.PI*g/(1e3*c.pulsateTime))/2);return this.pulse=b=a.Smooth(1,b),this.DrawSimple(h,d,e,f,b,1)},f.Active=function(d,a,b){var c=a>=this.x&&b>=this.y&&a<=this.x+this.w&&b<=this.y+this.h;return c?this.ts=this.ts||q():this.ts=null,c},f.PreDraw=f.PostDraw=f.LastDraw=x;function J(a,h,c,b,e,f,g,d,i,j,k,l,m,n){this.tc=a,this.image=null,this.text=h,this.text_original=n,this.line_widths=[],this.title=c.title||null,this.a=c,this.position=new s(b[0],b[1],b[2]),this.x=this.y=this.z=0,this.w=e,this.h=f,this.colour=g||a.textColour,this.bgColour=d||a.bgColour,this.bgRadius=i|0,this.bgOutline=j||this.colour,this.bgOutlineThickness=k|0,this.textFont=l||a.textFont,this.padding=m|0,this.sc=this.alpha=1,this.weighted=!a.weight,this.outline=new _(a,this),this.audio=null}d=J.prototype,d.Init=function(b){var a=this.tc;this.textHeight=a.textHeight,this.HasText()?this.Measure(a.ctxt,a):(this.w=this.iw,this.h=this.ih),this.SetShadowColour=a.shadowAlpha?this.SetShadowColourAlpha:this.SetShadowColourFixed,this.SetDraw(a)},d.Draw=x,d.HasText=function(){return this.text&&this.text[0].length>0},d.EqualTo=function(a){var b=a.getElementsByTagName('img');return this.a.href!=a.href?0:b.length?this.image.src==b[0].src:(a.innerText||a.textContent)==this.text_original},d.SetImage=function(a){this.image=this.fimage=a},d.SetAudio=function(a){this.audio=a,this.audio.load()},d.SetDraw=function(a){this.Draw=this.fimage?a.ie>7?this.DrawImageIE:this.DrawImage:this.DrawText,a.noSelect&&(this.CheckActive=x)},d.MeasureText=function(d){var a,e=this.text.length,b=0,c;for(a=0;a0?c=H(c,this.oimage.width,this.oimage.height):this.oimage=H(this.oimage,c.width,c.height)),c&&(this.fimage=c,l=this.fimage.width/b,j=this.fimage.height/b),this.SetDraw(a),a.txtOpt=!!this.fimage),this.h=j,this.w=l},d.SetFont=function(a,b,c,d){this.textFont=a,this.colour=b,this.bgColour=c,this.bgOutline=d,this.Measure(this.tc.ctxt,this.tc)},d.SetWeight=function(c){var b=this.tc,e=b.weightMode.split(/[, ]/),d,a,f=c.length;if(!this.HasText())return;this.weighted=!0;for(a=0;a0&&a.weightSizeMax>a.weightSizeMin?this.textHeight=a.weightSize*(a.weightSizeMin+(a.weightSizeMax-a.weightSizeMin)*c):this.textHeight=g(1,b*a.weightSize))},d.SetShadowColourFixed=function(a,b,c){a.shadowColor=b},d.SetShadowColourAlpha=function(a,b,c){a.shadowColor=aE(b,c)},d.DrawText=function(a,h,i){var e=this.tc,g=this.x,f=this.y,c=this.sc,b,d;a.globalAlpha=this.alpha,a.fillStyle=this.colour,e.shadow&&this.SetShadowColour(a,e.shadow,this.alpha),a.font=this.font,g+=h/c,f+=i/c-this.h/2;for(b=0;b{this.stopped?this.audio.pause():this.playing=1}),1}};function a(f,o,k){var d,i,b=c.getElementById(f),l=['id','class','innerHTML'];if(!b)throw 0;if(n(window.G_vmlCanvasManager)&&(b=window.G_vmlCanvasManager.initElement(b),this.ie=parseFloat(navigator.appVersion.split('MSIE')[1])),b&&(!b.getContext||!b.getContext('2d').fillText)){i=c.createElement('DIV');for(d=0;d0?a.scrollPause=~~this.scrollPause:this.scrollPause=0,this.minTags>0&&this.repeatTags<1&&(d=this.GetTags().length)&&(this.repeatTags=af(this.minTags/d)-1),this.transform=m.Identity(),this.startTime=this.time=q(),this.mx=this.my=-1,this.centreImage&&av(this),this.Animate=this.dragControl?this.AnimateDrag:this.AnimatePosition,this.animTiming=typeof a[this.animTiming]=='function'?a[this.animTiming]:a.Smooth,this.shadowBlur||this.shadowOffset[0]||this.shadowOffset[1]?(this.ctxt.shadowColor=this.shadow,this.shadow=this.ctxt.shadowColor,this.shadowAlpha=aD()):delete this.shadow,this.activeAudio===!1?e='off':this.activeAudio&&this.LoadAudio(),this.Load(),o&&this.hideTags&&function(b){a.loaded?b.HideTags():t('load',function(){b.HideTags()},window)}(this),this.yaw=this.initial?this.initial[0]*this.maxSpeed:0,this.pitch=this.initial?this.initial[1]*this.maxSpeed:0,this.tooltip?(this.ctitle=b.title,b.title='',this.tooltip=='native'?this.Tooltip=this.TooltipNative:(this.Tooltip=this.TooltipDiv,this.ttdiv||(this.ttdiv=c.createElement('div'),this.ttdiv.className=this.tooltipClass,this.ttdiv.style.position='absolute',this.ttdiv.style.zIndex=b.style.zIndex+1,t('mouseover',function(a){a.target.style.display='none'},this.ttdiv),c.body.appendChild(this.ttdiv)))):this.Tooltip=this.TooltipNone,!this.noMouse&&!j[f]){j[f]=[['mousemove',ad],['mouseout',an],['mouseup',aq],['touchstart',ar],['touchend',ac],['touchcancel',ac],['touchmove',au]],this.dragControl&&(j[f].push(['mousedown',ap]),j[f].push(['selectstart',x])),this.wheelZoom&&(j[f].push(['mousewheel',ab]),j[f].push(['DOMMouseScroll',ab])),this.scrollPause&&j[f].push(['scroll',aw,window]);for(d=0;dthis.max_weight[a])&&(this.max_weight[a]=c),(!this.min_weight[a]||cthis.min_weight[a]&&(g=1);if(g)for(b=0;b=d&&this.my>=e)return!0},b.ToggleAudio=function(){var a=this.audioOff||e&&e.state==='suspended';a||this.currentAudio&&this.currentAudio.StopAudio(),this.audioOff=!a},b.Draw=function(s){if(this.paused)return;var l=this.canvas,i=l.width,j=l.height,q=0,p=(s-this.time)*a.interval/1e3,h=i/2+this.offsetX,g=j/2+this.offsetY,d=this.ctxt,b,f,c,o=-1,e=this.taglist,k=e.length,t=this.active&&this.active.tag,m='',u=this.frontSelect,r=this.centreFunc==x,n;if(this.time=s,this.frozen&&this.drawn)return this.Animate(i,j,p);n=this.AnimateFixed(),d.setTransform(1,0,0,1,0,0);for(c=0;c=0&&this.my>=0&&this.taglist[c].CheckActive(d,h,g),f&&f.sc>q&&(!u||f.z<=0)&&(b=f,o=c,b.tag=this.taglist[c],q=f.sc);this.active=b}this.txtOpt||this.shadow&&this.SetShadow(d),d.clearRect(0,0,i,j);for(c=0;c=this.fadeIn?(this.fadeIn=0,this.fixedAlpha=1):this.fixedAlpha=b/this.fadeIn),this.fixedAnim)&&(this.fixedAnim.transform||(this.fixedAnim.transform=this.transform),a=this.fixedAnim,b=q()-a.t0,c=a.angle,d,e=this.animTiming(a.t,b),this.transform=a.transform,b>=a.t?(this.fixedCallbackTag=a.tag,this.fixedCallback=a.cb,this.fixedAnim=this.yaw=this.pitch=0):c*=e,d=m.Rotation(c,a.axis),this.transform=this.transform.mul(d),this.fixedAnim!=0)},b.AnimatePosition=function(g,h,f){var a=this,d=a.mx,e=a.my,b,c;!a.frozen&&d>=0&&e>=0&&db&&(a.yaw=c>a.z0?a.yaw*a.decel:0),!a.ly&&d>b&&(a.pitch=d>a.z0?a.pitch*a.decel:0)},b.Zoom=function(a){this.z2=this.z1*(1/a),this.drawn=0},b.Clicked=function(b){if(this.CheckAudioIcon()){this.ToggleAudio();return}var a=this.active;try{a&&a.tag&&(this.clickToFront===!1||this.clickToFront===null?a.tag.Clicked(b):this.TagToFront(a.tag,this.clickToFront,function(){a.tag.Clicked(b)},!0))}catch(a){}},b.Wheel=function(a){var b=this.zoom+this.zoomStep*(a?1:-1);this.zoom=h(this.zoomMax,g(this.zoomMin,b)),this.Zoom(this.zoom)},b.BeginDrag=function(a){this.down=K(a,this.canvas),a.cancelBubble=!0,a.returnValue=!1,a.preventDefault&&a.preventDefault()},b.Drag=function(e,a){if(this.dragControl&&this.down){var d=this.dragThreshold*this.dragThreshold,b=a.x-this.down.x,c=a.y-this.down.y;(this.dragging||b*b+c*c>d)&&(this.dx=b,this.dy=c,this.dragging=1,this.down=a)}return this.dragging},b.EndDrag=function(){var a=this.dragging;return this.dragging=this.down=null,a};function ah(a){var b=a.targetTouches[0],c=a.targetTouches[1];return E(w(c.pageX-b.pageX,2)+w(c.pageY-b.pageY,2))}b.BeginPinch=function(a){this.pinched=[ah(a),this.zoom],a.preventDefault&&a.preventDefault()},b.Pinch=function(d){var b,c,a=this.pinched;if(!a)return;c=ah(d),b=a[1]*c/a[0],this.zoom=h(this.zoomMax,g(this.zoomMin,b)),this.Zoom(this.zoom)},b.EndPinch=function(a){this.pinched=null},b.Pause=function(){this.paused=!0},b.Resume=function(){this.paused=!1},b.SetSpeed=function(a){this.initial=a,this.yaw=a[0]*this.maxSpeed,this.pitch=a[1]*this.maxSpeed},b.FindTag=function(a){if(!n(a))return null;if(n(a.index)&&(a=a.index),!B(a))return this.taglist[a];var c,d,b;n(a.id)?(c='id',d=a.id):n(a.text)&&(c='innerText',d=a.text);for(b=0;b