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