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