com.gargoylesoftware.htmlunit.javascript.host.canvas.CanvasRenderingContext2D Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2021 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.gargoylesoftware.htmlunit.javascript.host.canvas;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78;
import java.io.IOException;
import javax.imageio.ImageReader;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gargoylesoftware.htmlunit.html.HtmlImage;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.javascript.host.canvas.rendering.AwtRenderingBackend;
import com.gargoylesoftware.htmlunit.javascript.host.canvas.rendering.RenderingBackend;
import com.gargoylesoftware.htmlunit.javascript.host.canvas.rendering.RenderingBackend.WindingRule;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCanvasElement;
import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLImageElement;
import com.gargoylesoftware.htmlunit.util.MimeType;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
/**
* A JavaScript object for {@code CanvasRenderingContext2D}.
*
* @author Ahmed Ashour
* @author Marc Guillemot
* @author Frank Danek
* @author Ronald Brill
*/
@JsxClass
public class CanvasRenderingContext2D extends SimpleScriptable {
private static final Log LOG = LogFactory.getLog(CanvasRenderingContext2D.class);
private final HTMLCanvasElement canvas_;
private RenderingBackend renderingBackend_;
/**
* Default constructor.
*/
@JsxConstructor({CHROME, EDGE, FF, FF78})
public CanvasRenderingContext2D() {
canvas_ = null;
renderingBackend_ = null;
}
/**
* Constructs in association with {@link HTMLCanvasElement}.
* @param canvas the {@link HTMLCanvasElement}
*/
public CanvasRenderingContext2D(final HTMLCanvasElement canvas) {
canvas_ = canvas;
renderingBackend_ = null;
}
private RenderingBackend getRenderingBackend() {
if (renderingBackend_ == null) {
final int imageWidth = Math.max(1, canvas_.getWidth());
final int imageHeight = Math.max(1, canvas_.getHeight());
renderingBackend_ = new AwtRenderingBackend(imageWidth, imageHeight);
}
return renderingBackend_;
}
/**
* Specifies the alpha (transparency) value that is applied to shapes and images
* before they are drawn onto the canvas..
* @return the {@code globalAlpha} property
*/
@JsxGetter
public double getGlobalAlpha() {
return getRenderingBackend().getGlobalAlpha();
}
/**
* Sets the {@code globalAlpha} property.
* @param globalAlpha the {@code globalAlpha} property
*/
@JsxSetter
public void setGlobalAlpha(final double globalAlpha) {
getRenderingBackend().setGlobalAlpha(globalAlpha);
}
/**
* Returns the {@code fillStyle} property.
* @return the {@code fillStyle} property
*/
@JsxGetter
public Object getFillStyle() {
LOG.info("CanvasRenderingContext2D.getFillStyle() not yet implemented");
return null;
}
/**
* Sets the {@code fillStyle} property.
* @param fillStyle the {@code fillStyle} property
*/
@JsxSetter
public void setFillStyle(final String fillStyle) {
getRenderingBackend().setFillStyle(fillStyle);
}
/**
* Returns the {@code strokeStyle} property.
* @return the {@code strokeStyle} property
*/
@JsxGetter
public Object getStrokeStyle() {
LOG.info("CanvasRenderingContext2D.getStrokeStyle() not yet implemented");
return null;
}
/**
* Sets the {@code strokeStyle} property.
* @param strokeStyle the {@code strokeStyle} property
*/
@JsxSetter
public void setStrokeStyle(final String strokeStyle) {
getRenderingBackend().setStrokeStyle(strokeStyle);
}
/**
* Returns the {@code lineWidth} property.
* @return the {@code lineWidth} property
*/
@JsxGetter
public double getLineWidth() {
return getRenderingBackend().getLineWidth();
}
/**
* Sets the {@code lineWidth} property.
* @param lineWidth the {@code lineWidth} property
*/
@JsxSetter
public void setLineWidth(final Object lineWidth) {
if (!Undefined.isUndefined(lineWidth)) {
final double width = Context.toNumber(lineWidth);
if (!Double.isNaN(width)) {
getRenderingBackend().setLineWidth((int) width);
}
}
}
/**
* Draws an arc.
* @param x the x
* @param y the y
* @param radius the radius
* @param startAngle the start angle
* @param endAngle the end angle
* @param anticlockwise is anti-clockwise
*/
@JsxFunction
public void arc(final double x, final double y, final double radius, final double startAngle,
final double endAngle, final boolean anticlockwise) {
getRenderingBackend().arc(x, y, radius, startAngle, endAngle, anticlockwise);
}
/**
* Draws an arc.
* @param x1 the x1
* @param y1 the y1
* @param x2 the x2
* @param y2 the y2
* @param radius the radius
*/
@JsxFunction
public void arcTo(final double x1, final double y1, final double x2, final double y2,
final double radius) {
LOG.info("CanvasRenderingContext2D.arcTo() not yet implemented");
}
/**
* Begins the subpaths.
*/
@JsxFunction
public void beginPath() {
getRenderingBackend().beginPath();
}
/**
* Draws a cubic Bézier curve.
* @param cp1x the cp1x
* @param cp1y the cp1y
* @param cp2x the cp2x
* @param cp2y the cp2y
* @param x the x
* @param y the y
*/
@JsxFunction
public void bezierCurveTo(final double cp1x, final double cp1y, final double cp2x, final double cp2y,
final double x, final double y) {
getRenderingBackend().bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);
}
/**
* Clears the specified rectangular area.
* @param x the x
* @param y the y
* @param w the width
* @param h the height
*/
@JsxFunction
public void clearRect(final double x, final double y, final double w, final double h) {
getRenderingBackend().clearRect(x, y, w, h);
}
/**
* Creates a new clipping region.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
*/
@JsxFunction
public static void clip(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
if (!(thisObj instanceof CanvasRenderingContext2D)) {
throw Context.reportRuntimeError(
"CanvasRenderingContext2D.getImageData() failed - this is not a CanvasRenderingContext2D");
}
final CanvasRenderingContext2D canvas = (CanvasRenderingContext2D) thisObj;
RenderingBackend.WindingRule windingRule = WindingRule.NON_ZERO;
if (args.length == 1) {
final String windingRuleParam = ScriptRuntime.toString(args[0]);
if ("evenodd".contentEquals(windingRuleParam)) {
windingRule = WindingRule.EVEN_ODD;
}
canvas.getRenderingBackend().clip(windingRule, null);
}
if (args.length > 1) {
if (!(args[0] instanceof Path2D)) {
throw Context.reportRuntimeError(
"CanvasRenderingContext2D.clip() failed - the first parameter has to be a Path2D");
}
final String windingRuleParam = ScriptRuntime.toString(args[1]);
if ("evenodd".contentEquals(windingRuleParam)) {
windingRule = WindingRule.EVEN_ODD;
}
LOG.info("CanvasRenderingContext2D.clip(path, fillRule) not yet implemented");
// canvas.getRenderingBackend().clip(windingRule, (Path2D) args[0]);
}
canvas.getRenderingBackend().clip(WindingRule.NON_ZERO, null);
}
/**
* Closes the current subpath.
*/
@JsxFunction
public void closePath() {
getRenderingBackend().closePath();
}
/**
* Returns the {@code ImageData} object.
* this may accept a variable number of arguments.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @return the {@code ImageData} object
*/
@JsxFunction
public static ImageData createImageData(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
if (!(thisObj instanceof CanvasRenderingContext2D)) {
throw Context.reportRuntimeError(
"CanvasRenderingContext2D.getImageData() failed - this is not a CanvasRenderingContext2D");
}
final CanvasRenderingContext2D canvas = (CanvasRenderingContext2D) thisObj;
if (args.length > 0 && args[0] instanceof ImageData) {
final ImageData imageDataParameter = (ImageData) args[0];
final ImageData imageData = new ImageData(null,
0, 0, imageDataParameter.getWidth(), imageDataParameter.getHeight());
imageData.setParentScope(canvas.getParentScope());
imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
return imageData;
}
if (args.length > 1) {
final int width = Math.abs((int) ScriptRuntime.toInteger(args, 0));
final int height = Math.abs((int) ScriptRuntime.toInteger(args, 1));
final ImageData imageData = new ImageData(null, 0, 0, width, height);
imageData.setParentScope(canvas.getParentScope());
imageData.setPrototype(canvas.getPrototype(imageData.getClass()));
return imageData;
}
throw Context.reportRuntimeError(
"CanvasRenderingContext2D.getImageData() failed - "
+ "wrong parameters given (" + StringUtils.join(args, ", ") + ")");
}
/**
* Creates linear gradient.
* @param x0 the x0
* @param y0 the y0
* @param r0 the r0
* @param x1 the x1
* @param y1 the y1
* @param r1 the r1
* @return the new CanvasGradient
*/
@JsxFunction
public CanvasGradient createLinearGradient(final double x0, final double y0, final double r0, final double x1,
final Object y1, final Object r1) {
final CanvasGradient canvasGradient = new CanvasGradient();
canvasGradient.setParentScope(getParentScope());
canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
return canvasGradient;
}
/**
* Creates a pattern.
*/
@JsxFunction
public void createPattern() {
LOG.info("CanvasRenderingContext2D.createPattern() not yet implemented");
}
/**
* Creates a gradient.
* @param x0 the x axis of the coordinate of the start circle
* @param y0 the y axis of the coordinate of the start circle
* @param r0 the radius of the start circle
* @param x1 the x axis of the coordinate of the end circle
* @param y1 the y axis of the coordinate of the end circle
* @param r1 the radius of the end circle
* @return the new CanvasGradient
*/
@JsxFunction
public CanvasGradient createRadialGradient(final double x0, final double y0,
final double r0, final double x1, final double y1, final double r1) {
final CanvasGradient canvasGradient = new CanvasGradient();
canvasGradient.setParentScope(getParentScope());
canvasGradient.setPrototype(getPrototype(canvasGradient.getClass()));
return canvasGradient;
}
/**
* Draws images onto the canvas.
*
* @param image an element to draw into the context
* @param sx the X coordinate of the top left corner of the sub-rectangle of the source image
* to draw into the destination context
* @param sy the Y coordinate of the top left corner of the sub-rectangle of the source image
* to draw into the destination context
* @param sWidth the width of the sub-rectangle of the source image to draw into the destination context
* @param sHeight the height of the sub-rectangle of the source image to draw into the destination context
* @param dx the X coordinate in the destination canvas at which to place the top-left corner of the source image
* @param dy the Y coordinate in the destination canvas at which to place the top-left corner of the source image
* @param dWidth the width to draw the image in the destination canvas. This allows scaling of the drawn image
* @param dHeight the height to draw the image in the destination canvas. This allows scaling of the drawn image
*/
@JsxFunction
@SuppressWarnings("unused")
public void drawImage(final Object image, final int sx, final int sy, final Object sWidth, final Object sHeight,
final Object dx, final Object dy, final Object dWidth, final Object dHeight) {
if (image instanceof HTMLImageElement) {
final HTMLImageElement imageElem = (HTMLImageElement) image;
try {
final ImageReader imageReader = ((HtmlImage) imageElem.getDomNodeOrDie()).getImageReader();
// 3 arguments
// void ctx.drawImage(image, dx, dy);
if (Undefined.isUndefined(sWidth)) {
getRenderingBackend().drawImage(imageReader, 0, 0, null, null, sx, sy, null, null);
}
// 5 arguments
// void ctx.drawImage(image, dx, dy, dWidth, dHeight);
else if (Undefined.isUndefined(dx)) {
final int dWidthI = ScriptRuntime.toInt32(sWidth);
final int dHeightI = ScriptRuntime.toInt32(sHeight);
getRenderingBackend().drawImage(imageReader, 0, 0, null, null, sx, sy, dWidthI, dHeightI);
}
// all 9 arguments
// void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);
else {
final int sWidthI = ScriptRuntime.toInt32(sWidth);
final int sHeightI = ScriptRuntime.toInt32(sHeight);
final int dxI = ScriptRuntime.toInt32(dx);
final int dyI = ScriptRuntime.toInt32(dy);
final int dWidthI = ScriptRuntime.toInt32(dWidth);
final int dHeightI = ScriptRuntime.toInt32(dHeight);
getRenderingBackend().drawImage(imageReader,
sx, sy, sWidthI, sHeightI, dxI, dyI, dWidthI, dHeightI);
}
}
catch (final IOException ioe) {
LOG.info("There is no ImageReader available for you imgage with src '" + imageElem.getSrc() + "'"
+ "Please have a look at https://htmlunit.sourceforge.io/images-howto.html "
+ "for a possible solution.");
}
}
}
/**
* Returns the Data URL.
*
* @param type an optional type
* @return the dataURL
*/
public String toDataURL(String type) {
try {
if (type == null) {
type = MimeType.IMAGE_PNG;
}
return "data:" + type + ";base64," + getRenderingBackend().encodeToString(type);
}
catch (final IOException ioe) {
throw Context.throwAsScriptRuntimeEx(ioe);
}
}
/**
* Paints the specified ellipse.
* @param x the x
* @param y the y
* @param radiusX the radiusX
* @param radiusY the radiusY
* @param rotation the rotation
* @param startAngle the startAngle
* @param endAngle the endAngle
* @param anticlockwise the anticlockwise
*/
@JsxFunction({CHROME, EDGE, FF, FF78})
public void ellipse(final double x, final double y,
final double radiusX, final double radiusY,
final double rotation, final double startAngle, final double endAngle,
final boolean anticlockwise) {
getRenderingBackend().ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise);
}
/**
* Fills the shape.
*/
@JsxFunction
public void fill() {
getRenderingBackend().fill();
}
/**
* Paints the specified rectangular area.
* @param x the x
* @param y the y
* @param w the width
* @param h the height
*/
@JsxFunction
public void fillRect(final int x, final int y, final int w, final int h) {
getRenderingBackend().fillRect(x, y, w, h);
}
/**
* Fills a given text at the given (x, y) position.
* @param text the text
* @param x the x
* @param y the y
*/
@JsxFunction
public void fillText(final String text, final double x, final double y) {
getRenderingBackend().fillText(text, x, y);
}
/**
* Returns the {@code ImageData} object.
* @param sx x
* @param sy y
* @param sw width
* @param sh height
* @return the {@code ImageData} object
*/
@JsxFunction
public ImageData getImageData(final int sx, final int sy, final int sw, final int sh) {
final ImageData imageData = new ImageData(getRenderingBackend(), sx, sy, sw, sh);
imageData.setParentScope(getParentScope());
imageData.setPrototype(getPrototype(imageData.getClass()));
return imageData;
}
/**
* Dummy placeholder.
*/
@JsxFunction
public void getLineDash() {
LOG.info("CanvasRenderingContext2D.getLineDash() not yet implemented");
}
/**
* Dummy placeholder.
*/
@JsxFunction
public void getLineData() {
LOG.info("CanvasRenderingContext2D.getLineData() not yet implemented");
}
/**
* Dummy placeholder.
*/
@JsxFunction
public void isPointInPath() {
LOG.info("CanvasRenderingContext2D.isPointInPath() not yet implemented");
}
/**
* Connect the last point to the given point.
* @param x the x
* @param y the y
*/
@JsxFunction
public void lineTo(final double x, final double y) {
getRenderingBackend().lineTo(x, y);
}
/**
* Calculate TextMetrics for the given text.
* @param text the text to measure
* @return the text metrics
*/
@JsxFunction
public TextMetrics measureText(final Object text) {
if (text == null || Undefined.isUndefined(text)) {
throw Context.throwAsScriptRuntimeEx(
new RuntimeException("Missing argument for CanvasRenderingContext2D.measureText()."));
}
final String textValue = Context.toString(text);
// TODO take font into account
final int width = textValue.length() * getBrowserVersion().getPixesPerChar();
final TextMetrics metrics = new TextMetrics(width);
metrics.setParentScope(getParentScope());
metrics.setPrototype(getPrototype(metrics.getClass()));
return metrics;
}
/**
* Creates a new subpath.
* @param x the x
* @param y the y
*/
@JsxFunction
public void moveTo(final double x, final double y) {
getRenderingBackend().moveTo(x, y);
}
/**
* Paints data from the given ImageData object onto the canvas.
* @param imageData an ImageData object containing the array of pixel values
* @param dx horizontal position (x coordinate) at which to place the image data in the destination canvas
* @param dy vertical position (y coordinate) at which to place the image data in the destination canvas
* @param dirtyX horizontal position (x coordinate) of the top-left corner
* from which the image data will be extracted. Defaults to 0.
* @param dirtyY vertical position (y coordinate) of the top-left corner
* from which the image data will be extracted. Defaults to 0.
* @param dirtyWidth width of the rectangle to be painted.
* Defaults to the width of the image data.
* @param dirtyHeight height of the rectangle to be painted.
* Defaults to the height of the image data.
*/
@JsxFunction
public void putImageData(final ImageData imageData,
final int dx, final int dy, final Object dirtyX, final Object dirtyY,
final Object dirtyWidth, final Object dirtyHeight) {
int dirtyXArg = 0;
int dirtyYArg = 0;
int dirtyWidthArg = imageData.getWidth();
int dirtyHeightArg = imageData.getHeight();
if (!Undefined.isUndefined(dirtyX)) {
dirtyXArg = (int) ScriptRuntime.toInteger(dirtyX);
if (Undefined.isUndefined(dirtyY)
|| Undefined.isUndefined(dirtyWidth)
|| Undefined.isUndefined(dirtyHeight)) {
throw Context.reportRuntimeError(
"CanvasRenderingContext2D.putImageData() failed - seven parameters expected");
}
dirtyYArg = (int) ScriptRuntime.toInteger(dirtyY);
dirtyWidthArg = (int) ScriptRuntime.toInteger(dirtyWidth);
dirtyHeightArg = (int) ScriptRuntime.toInteger(dirtyHeight);
}
getRenderingBackend().putImageData(imageData, dx, dy, dirtyXArg, dirtyYArg, dirtyWidthArg, dirtyHeightArg);
}
/**
* Draws a quadratic Bézier curve.
* @param controlPointX the x-coordinate of the control point
* @param controlPointY the y-coordinate of the control point
* @param endPointX the x-coordinate of the end point
* @param endPointY the y-coordinate of the end point
*/
@JsxFunction
public void quadraticCurveTo(final double controlPointX, final double controlPointY,
final double endPointX, final double endPointY) {
getRenderingBackend().quadraticCurveTo(controlPointX, controlPointY, endPointX, endPointY);
}
/**
* Renders a rectangle.
* @param x the x
* @param y the y
* @param w the width
* @param h the height
*/
@JsxFunction
public void rect(final double x, final double y, final double w, final double h) {
getRenderingBackend().rect(x, y, w, h);
}
/**
* Pops state stack and restore state.
*/
@JsxFunction
public void restore() {
getRenderingBackend().restore();
}
/**
* Adds a rotation to the transformation matrix.
* @param angle the angle
*/
@JsxFunction
public void rotate(final double angle) {
getRenderingBackend().rotate(angle);
}
/**
* Pushes state on state stack.
*/
@JsxFunction
public void save() {
getRenderingBackend().save();
}
/**
* Changes the transformation matrix to apply a scaling transformation with the given characteristics.
* @param x the scale factor in the horizontal direction
* @param y the scale factor in the vertical direction
*/
@JsxFunction
public void scale(final Object x, final Object y) {
LOG.info("CanvasRenderingContext2D.scale() not yet implemented");
}
/**
* Dummy placeholder.
*/
@JsxFunction
public void setLineDash() {
LOG.info("CanvasRenderingContext2D.setLineDash() not yet implemented");
}
/**
* Resets (overrides) the current transformation to the identity matrix,
* and then invokes a transformation described by the arguments of this method.
* This lets you scale, rotate, translate (move), and skew the context.
* @param m11 Horizontal scaling. A value of 1 results in no scaling
* @param m12 Vertical skewing
* @param m21 Horizontal skewing
* @param m22 Vertical scaling. A value of 1 results in no scaling
* @param dx Horizontal translation (moving)
* @param dy Vertical translation (moving).
*/
@JsxFunction
public void setTransform(final double m11, final double m12,
final double m21, final double m22, final double dx, final double dy) {
getRenderingBackend().setTransform(m11, m12, m21, m22, dx, dy);
}
/**
* Calculates the strokes of all the subpaths of the current path.
*/
@JsxFunction
public void stroke() {
getRenderingBackend().stroke();
}
/**
* Strokes the specified rectangular area.
* @param x the x
* @param y the y
* @param w the width
* @param h the height
*/
@JsxFunction
public void strokeRect(final int x, final int y, final int w, final int h) {
getRenderingBackend().strokeRect(x, y, w, h);
}
/**
* Dummy placeholder.
*/
@JsxFunction
public void strokeText() {
LOG.info("CanvasRenderingContext2D.strokeText() not yet implemented");
}
/**
* Multiplies the current transformation with the matrix described by the
* arguments of this method. This lets you scale, rotate, translate (move),
* and skew the context.
* @param m11 Horizontal scaling. A value of 1 results in no scaling
* @param m12 Vertical skewing
* @param m21 Horizontal skewing
* @param m22 Vertical scaling. A value of 1 results in no scaling
* @param dx Horizontal translation (moving)
* @param dy Vertical translation (moving).
*/
@JsxFunction
public void transform(final double m11, final double m12,
final double m21, final double m22, final double dx, final double dy) {
getRenderingBackend().transform(m11, m12, m21, m22, dx, dy);
}
/**
* Changes the transformation matrix to apply a translation transformation with the given characteristics.
* @param x the translation distance in the horizontal direction
* @param y the translation distance in the vertical direction
*/
@JsxFunction
public void translate(final int x, final int y) {
getRenderingBackend().translate(x, y);
}
/**
* Returns the associated {@link HTMLCanvasElement}.
* @return the associated {@link HTMLCanvasElement}
*/
@JsxGetter
public HTMLCanvasElement getCanvas() {
return canvas_;
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy