      1 /*
      2     Copyright 2008-2022
      3         Matthias Ehmann,
      4         Michael Gerhaeuser,
      5         Carsten Miller,
      6         Bianca Valentin,
      7         Alfred Wassermann,
      8         Peter Wilfahrt
      9
     10     This file is part of JSXGraph.
     11
     12     JSXGraph is free software dual licensed under the GNU LGPL or MIT License.
     13
     14     You can redistribute it and/or modify it under the terms of the
     15
     16       * GNU Lesser General Public License as published by
     17         the Free Software Foundation, either version 3 of the License, or
     18         (at your option) any later version
     19       OR
     20       * MIT License: https://github.com/jsxgraph/jsxgraph/blob/master/LICENSE.MIT
     21
     22     JSXGraph is distributed in the hope that it will be useful,
     23     but WITHOUT ANY WARRANTY; without even the implied warranty of
     24     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     25     GNU Lesser General Public License for more details.
     26
     27     You should have received a copy of the GNU Lesser General Public License and
     28     the MIT License along with JSXGraph. If not, see <http://www.gnu.org/licenses/>
     29     and <http://opensource.org/licenses/MIT/>.
     30  */
     31
     32
     33 /*global JXG: true, define: true, window: true*/
     34 /*jslint nomen: true, plusplus: true*/
     35
     36 /* depends:
     37  jxg
     38  base/constants
     39  base/coords
     40  base/element
     41  math/math
     42  utils/type
     43  */
     44
     45 /**
     46  * @fileoverview In this file the ForeignObject element is defined.
     47  */
     48
     49  define([
     50     'jxg', 'base/constants', 'base/coords', 'base/element', 'math/math', 'utils/type', 'base/coordselement'
     51 ], function (JXG, Const, Coords, GeometryElement, Mat, Type, CoordsElement) {
     52
     53     "use strict";
     54
     55     /**
     56      * Construct and handle SVG foreignObjects.
     57      *
     58      * @class Creates a new foreignObject object. Do not use this constructor to create a foreignObject. Use {@link JXG.Board#create} with
     59      * type {@link foreignobject} instead.
     60      * @augments JXG.GeometryElement
     61      * @augments JXG.CoordsElement
     62      * @param {string|JXG.Board} board The board the new foreignObject is drawn on.
     63      * @param {Array} coordinates An array with the user coordinates of the foreignObject.
     64      * @param {Object} attributes An object containing visual and - optionally - a name and an id.
     65      * @param {string|function} url An URL string or a function returning an URL string.
     66      * @param  {Array} size Array containing width and height of the foreignObject in user coordinates.
     67      *
     68      */
     69      JXG.ForeignObject = function (board, coords, attributes, content, size) {
     70         this.constructor(board, attributes, Const.OBJECT_TYPE_FOREIGNOBJECT, Const.OBJECT_CLASS_OTHER);
     71         this.element = this.board.select(attributes.anchor);
     72         this.coordsConstructor(coords);
     73
     74         this._useUserSize = false;
     75
     76         /**
     77          * Array of length two containing [width, height] of the foreignObject in pixel.
     78          * @type Array
     79          */
     80         this.size = [1, 1];
     81         if (Type.exists(size) && size.length > 0) {
     82             this._useUserSize = true;
     83
     84             this.W = Type.createFunction(size[0], this.board, '');
     85             this.H = Type.createFunction(size[1], this.board, '');
     86             this.usrSize = [this.W(), this.H()];
     87         }
     88
     89         // this.size = [Math.abs(this.usrSize[0] * board.unitX), Math.abs(this.usrSize[1] * board.unitY)];
     90
     91         /**
     92          * 'href' of the foreignObject.
     93          * @type {string}
     94          */
     95         this.content = content;
     96
     97         this.elType = 'foreignobject';
     98
     99         // span contains the anchor point and the two vectors
    100         // spanning the foreignObject rectangle.
    101         // this.span = [
    102         //     this.coords.usrCoords.slice(0),
    103         //     [this.coords.usrCoords[0], this.W(), 0],
    104         //     [this.coords.usrCoords[0], 0, this.H()]
    105         // ];
    106         //this.parent = board.select(attributes.anchor);
    107
    108         this.id = this.board.setId(this, 'Im');
    109
    110         this.board.renderer.drawForeignObject(this);
    111         this.board.finalizeAdding(this);
    112
    113         this.methodMap = JXG.deepCopy(this.methodMap, {
    114             addTransformation: 'addTransform',
    115             trans: 'addTransform'
    116         });
    117     };
    118
    119     JXG.ForeignObject.prototype = new GeometryElement();
    120     Type.copyPrototypeMethods(JXG.ForeignObject, CoordsElement, 'coordsConstructor');
    121
    122     JXG.extend(JXG.ForeignObject.prototype, /** @lends JXG.ForeignObject.prototype */ {
    123
    124         /**
    125          * Checks whether (x,y) is over or near the image;
    126          * @param {Number} x Coordinate in x direction, screen coordinates.
    127          * @param {Number} y Coordinate in y direction, screen coordinates.
    128          * @returns {Boolean} True if (x,y) is over the image, False otherwise.
    129          */
    130         hasPoint: function (x, y) {
    131             var dx, dy, r, type, prec,
    132                 c, v, p, dot,
    133                 len = this.transformations.length;
    134
    135                 if (Type.isObject(Type.evaluate(this.visProp.precision))) {
    136                     type = this.board._inputDevice;
    137                     prec = Type.evaluate(this.visProp.precision[type]);
    138                 } else {
    139                     // 'inherit'
    140                     prec = this.board.options.precision.hasPoint;
    141                 }
    142
    143             // Easy case: no transformation
    144             if (len === 0) {
    145                 dx = x - this.coords.scrCoords[1];
    146                 dy = this.coords.scrCoords[2] - y;
    147                 r = prec;
    148
    149                 return dx >= -r && dx - this.size[0] <= r &&
    150                        dy >= -r && dy - this.size[1] <= r;
    151             }
    152
    153             // foreignObject is transformed
    154             c = new Coords(Const.COORDS_BY_SCREEN, [x, y], this.board);
    155             // v is the vector from anchor point to the drag point
    156             c = c.usrCoords;
    157             v = [c[0] - this.span[0][0],
    158                 c[1] - this.span[0][1],
    159                 c[2] - this.span[0][2]];
    160             dot = Mat.innerProduct;   // shortcut
    161
    162             // Project the drag point to the sides.
    163             p = dot(v, this.span[1]);
    164             if (0 <= p && p <= dot(this.span[1], this.span[1])) {
    165                 p = dot(v, this.span[2]);
    166
    167                 if (0 <= p && p <= dot(this.span[2], this.span[2])) {
    168                     return true;
    169                 }
    170             }
    171             return false;
    172         },
    173
    174         /**
    175          * Recalculate the coordinates of lower left corner and the width and height.
    176          *
    177          * @returns {JXG.ForeignObject} A reference to the element
    178          * @private
    179          */
    180         update: function (fromParent) {
    181             if (!this.needsUpdate) {
    182                 return this;
    183             }
    184             this.updateCoords(fromParent);
    185             this.updateSize();
    186             // this.updateSpan();
    187             return this;
    188         },
    189
    190         /**
    191          * Send an update request to the renderer.
    192          * @private
    193          */
    194         updateRenderer: function () {
    195             return this.updateRendererGeneric('updateForeignObject');
    196         },
    197
    198         /**
    199          * Updates the internal arrays containing size of the foreignObject.
    200          * @returns {JXG.ForeignObject} A reference to the element
    201          * @private
    202          */
    203         updateSize: function () {
    204             var bb = [0, 0];
    205
    206             if (this._useUserSize) {
    207                 this.usrSize = [this.W(), this.H()];
    208                 this.size = [Math.abs(this.usrSize[0] * this.board.unitX),
    209                              Math.abs(this.usrSize[1] * this.board.unitY)];
    210             } else {
    211                 if (this.rendNode.hasChildNodes()) {
    212                     bb = this.rendNode.childNodes[0].getBoundingClientRect();
    213                     this.size = [bb.width, bb.height];
    214                 }
    215             }
    216
    217             return this;
    218         },
    219
    220         /**
    221          * Update the anchor point of the foreignObject, i.e. the lower left corner
    222          * and the two vectors which span the rectangle.
    223          * @returns {JXG.ForeignObject} A reference to the element
    224          * @private
    225          *
    226          */
    227         updateSpan: function () {
    228             var i, j, len = this.transformations.length, v = [];
    229
    230             if (len === 0) {
    231                 this.span = [[this.Z(), this.X(), this.Y()],
    232                     [this.Z(), this.W(), 0],
    233                     [this.Z(), 0, this.H()]];
    234             } else {
    235                 // v contains the three defining corners of the rectangle/image
    236                 v[0] = [this.Z(), this.X(), this.Y()];
    237                 v[1] = [this.Z(), this.X() + this.W(), this.Y()];
    238                 v[2] = [this.Z(), this.X(), this.Y() + this.H()];
    239
    240                 // Transform the three corners
    241                 for (i = 0; i < len; i++) {
    242                     for (j = 0; j < 3; j++) {
    243                         v[j] = Mat.matVecMult(this.transformations[i].matrix, v[j]);
    244                     }
    245                 }
    246                 // Normalize the vectors
    247                 for (j = 0; j < 3; j++) {
    248                     v[j][1] /= v[j][0];
    249                     v[j][2] /= v[j][0];
    250                     v[j][0] /= v[j][0];
    251                 }
    252                 // Compute the two vectors spanning the rectangle
    253                 // by subtracting the anchor point.
    254                 for (j = 1; j < 3; j++) {
    255                     v[j][0] -= v[0][0];
    256                     v[j][1] -= v[0][1];
    257                     v[j][2] -= v[0][2];
    258                 }
    259                 this.span = v;
    260             }
    261
    262             return this;
    263         },
    264
    265         addTransform: function (transform) {
    266             var i;
    267
    268             if (Type.isArray(transform)) {
    269                 for (i = 0; i < transform.length; i++) {
    270                     this.transformations.push(transform[i]);
    271                 }
    272             } else {
    273                 this.transformations.push(transform);
    274             }
    275
    276             return this;
    277         },
    278
    279         // Documented in element.js
    280         getParents: function () {
    281             var p = [this.url, [this.Z(), this.X(), this.Y()], this.usrSize];
    282
    283             if (this.parents.length !== 0) {
    284                 p = this.parents;
    285             }
    286
    287             return p;
    288         },
    289
    290         /**
    291          * Set the width and height of the foreignObject. After setting a new size,
    292          * board.update() or foreignobject.fullUpdate()
    293          * has to be called to make the change visible.
    294          * @param  {number, function, string} width  Number, function or string
    295          *                            that determines the new width of the foreignObject
    296          * @param  {number, function, string} height Number, function or string
    297          *                            that determines the new height of the foreignObject
    298          * @returns {JXG.ForeignObject} A reference to the element
    299          *
    300          */
    301         setSize: function(width, height) {
    302             this.W = Type.createFunction(width, this.board, '');
    303             this.H = Type.createFunction(height, this.board, '');
    304             this._useUserSize = true;
    305
    306             return this;
    307         },
    308
    309         /**
    310          * Returns the width of the foreignObject in user coordinates.
    311          * @returns {number} width of the image in user coordinates
    312          */
    313         W: function() {},  // Needed for docs, defined in constructor
    314
    315         /**
    316          * Returns the height of the foreignObject in user coordinates.
    317          * @returns {number} height of the image in user coordinates
    318          */
    319         H: function() {}  // Needed for docs, defined in constructor
    320     });
    321
    322     /**
    323      * @class This element is used to provide a constructor for arbitrary content in
    324      * an SVG foreignObject container.
    325      * <p>
    326      * Instead of board.create('foreignobject') the shortcut board.create('fo') may be used.
    327      *
    328      * @pseudo
    329      * @description
    330      * @name ForeignObject
    331      * @augments JXG.ForeignObject
    332      * @constructor
    333      * @type JXG.ForeignObject
    334      *
    335      * @param {String} content HTML content of the foreignObject. May also be <video> or <iframe>
    336      * @param {Array} position Position of the foreignObject given by [x, y] in user coordinates. Same as for images.
    337      * @param {Array} [size] (Optional) argument size of the foreignObject in user coordinates. If not given, size is specified by the HTML attributes
    338      * or CSS properties of the content.
    339      *
    340      * @see Image
    341      *
    342      * @example
    343      * var p = board.create('point', [1, 7], {size: 16});
    344      * var fo = board.create('foreignobject', [
    345      *     '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>',
    346      *     [0, -3], [9, 6]],
    347      *     {layer: 8, fixed: true}
    348      *  );
    349      *
    350      * </pre><div id="JXG0c122f2c-3671-4a28-80a9-f4c523eeda89" class="jxgbox" style="width: 500px; height: 500px;"></div>
    351      * <script type="text/javascript">
    352      *     (function() {
    353      *         var board = JXG.JSXGraph.initBoard('JXG0c122f2c-3671-4a28-80a9-f4c523eeda89',
    354      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    355      *     var p = board.create('point', [1, 7], {size: 16});
    356      *     var fo = board.create('foreignobject', [
    357      *         '<video width="300" height="200" src="https://eucbeniki.sio.si/vega2/278/Video_metanje_oge_.mp4" type="html5video" controls>',
    358      *         [0, -3], [9, 6]],
    359      *         {layer: 8, fixed: true}
    360      *      );
    361      *
    362      *     })();
    363      *
    364      * </script><pre>
    365      *
    366      * @example
    367      * var p = board.create('point', [1, 7], {size: 16});
    368      * var fo = board.create('fo', [
    369      *     '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>',
    370      *     [-7, -6]],
    371      *     {layer: 1, fixed: false}
    372      *  );
    373      *
    374      * </pre><div id="JXG1759c868-1a4a-4767-802c-91f84902e3ec" class="jxgbox" style="width: 500px; height: 500px;"></div>
    375      * <script type="text/javascript">
    376      *     (function() {
    377      *         var board = JXG.JSXGraph.initBoard('JXG1759c868-1a4a-4767-802c-91f84902e3ec',
    378      *             {boundingbox: [-8, 8, 8,-8], axis: true, showcopyright: false, shownavigation: false});
    379      *     var p = board.create('point', [1, 7], {size: 16});
    380      *     var fo = board.create('foreignobject', [
    381      *         '<div style="background-color:blue; color: yellow; padding:20px; width:200px; height:50px; ">Hello</div>',
    382      *         [-7, -6]],
    383      *         {layer: 1, fixed: false}
    384      *      );
    385      *
    386      *     })();
    387      *
    388      * </script><pre>
    389      *
    390      * @example
    391      * board.renderer.container.style.backgroundColor = 'lightblue';
    392      * var points = [];
    393      * points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) );
    394      * points.push( board.create('point', [0, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) );
    395      * points.push( board.create('point', [2, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) );
    396      *
    397      * var fo = board.create('fo', [
    398      *     '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>',
    399      *     [-6, -4], [12, 8]],
    400      *     {layer: 0, fixed: true}
    401      *  );
    402      *
    403      * var f = JXG.Math.Numerics.lagrangePolynomial(points);
    404      * var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8});
    405      *
    406      * </pre><div id="JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f" class="jxgbox" style="width: 500px; height: 500px;"></div>
    407      * <script type="text/javascript">
    408      *     (function() {
    409      *         var board = JXG.JSXGraph.initBoard('JXGc3fc5520-13aa-4f66-abaa-42e9dc3fbf3f',
    410      *             {boundingbox: [-6,4,6,-4], axis: true, showcopyright: false, shownavigation: false});
    411      *     board.renderer.container.style.backgroundColor = 'lightblue';
    412      *     var points = [];
    413      *     points.push( board.create('point', [-2, 3.5], {fixed:false,color: 'yellow', size: 6,name:'6 am'}) );
    414      *     points.push( board.create('point', [0, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'12 pm'}) );
    415      *     points.push( board.create('point', [2, 3.5],  {fixed:false,color: 'yellow', size: 6,name:'6 pm'}) );
    416      *
    417      *     var fo = board.create('fo', [
    418      *         '<video width="100%" height="100%" src="https://benedu.net/moodle/aaimg/ajx_img/astro/tr/1vd.mp4" type="html5video" controls>',
    419      *         [-6, -4], [12, 8]],
    420      *         {layer: 0, fixed: true}
    421      *      );
    422      *
    423      *     var f = JXG.Math.Numerics.lagrangePolynomial(points);
    424      *     var graph = board.create('functiongraph', [f, -10, 10], {fixed:true,strokeWidth:3, layer: 8});
    425      *
    426      *     })();
    427      *
    428      * </script><pre>
    429      *
    430      * Video "24-hour time-lapse in Cascais, Portugal. Produced by Nuno Miguel Duarte" adapted from
    431      * <a href="https://www.pbslearningmedia.org/resource/buac18-k2-sci-ess-sunposition/changing-position-of-the-sun-in-the-sky/">https://www.pbslearningmedia.org/resource/buac18-k2-sci-ess-sunposition/changing-position-of-the-sun-in-the-sky/</a>,
    432      * ©2016 Nuno Miguel Duarte.
    433      *
    434      */
    435     JXG.createForeignObject = function (board, parents, attributes) {
    436         var attr, fo,
    437             content = parents[0],
    438             coords = parents[1],
    439             size = [];
    440
    441         if (parents.length >= 2) {
    442             size = parents[2];
    443         }
    444
    445         attr = Type.copyAttributes(attributes, board.options, 'foreignobject');
    446         fo = CoordsElement.create(JXG.ForeignObject, board, coords, attr, content, size);
    447         if (!fo) {
    448             throw new Error("JSXGraph: Can't create foreignObject with parent types '" +
    449                     (typeof parents[0]) + "' and '" + (typeof parents[1]) + "'." +
    450                     "\nPossible parent types: [string, [x, y], [w, h]], [string, [x, y]], [element,transformation]");
    451         }
    452
    453         return fo;
    454     };
    455
    456     JXG.registerElement('foreignobject', JXG.createForeignObject);
    457     JXG.registerElement('fo', JXG.createForeignObject);
    458
    459     return {
    460         ForeignObject: JXG.ForeignObject,
    461         createForeignobject: JXG.createForeignObject
    462     };
    463 });
    464