Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/server/actors/highlighters/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 21 kB image not shown  

Quelle  measuring-tool.js   Sprache: JAVA

 
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */


"use strict";

const EventEmitter = require("resource://devtools/shared/event-emitter.js");
const {
  getCurrentZoom,
  getWindowDimensions,
  setIgnoreLayoutChanges,
} = require("resource://devtools/shared/layout/utils.js");
const {
  CanvasFrameAnonymousContentHelper,
} = require("resource://devtools/server/actors/highlighters/utils/markup.js");

// Hard coded value about the size of measuring tool label, in order to
// position and flip it when is needed.
const LABEL_SIZE_MARGIN = 8;
const LABEL_SIZE_WIDTH = 80;
const LABEL_SIZE_HEIGHT = 52;
const LABEL_POS_MARGIN = 4;
const LABEL_POS_WIDTH = 40;
const LABEL_POS_HEIGHT = 34;
const LABEL_TYPE_SIZE = "size";
const LABEL_TYPE_POSITION = "position";

// List of all DOM Events subscribed directly to the document from the
// Measuring Tool highlighter
const DOM_EVENTS = [
  "mousedown",
  "mousemove",
  "mouseup",
  "mouseleave",
  "scroll",
  "pagehide",
  "keydown",
  "keyup",
];

const SIDES = ["top""right""bottom""left"];
const HANDLERS = [...SIDES, "topleft""topright""bottomleft""bottomright"];
const HANDLER_SIZE = 6;
const HIGHLIGHTED_HANDLER_CLASSNAME = "highlight";

const IS_OSX = Services.appinfo.OS === "Darwin";

/**
 * The MeasuringToolHighlighter is used to measure distances in a content page.
 * It allows users to click and drag with their mouse to draw an area whose
 * dimensions will be displayed in a tooltip next to it.
 * This allows users to measure distances between elements on a page.
 */

class MeasuringToolHighlighter {
  constructor(highlighterEnv) {
    this.env = highlighterEnv;
    this.markup = new CanvasFrameAnonymousContentHelper(
      highlighterEnv,
      this._buildMarkup.bind(this)
    );
    this.isReady = this.markup.initialize();

    this.rect = { x: 0, y: 0, w: 0, h: 0 };
    this.mouseCoords = { x: 0, y: 0 };

    const { pageListenerTarget } = highlighterEnv;

    // Register the measuring tool instance to all events we're interested in.
    DOM_EVENTS.forEach(type => pageListenerTarget.addEventListener(type, this));
  }

  ID_CLASS_PREFIX = "measuring-tool-";

  _buildMarkup() {
    const prefix = this.ID_CLASS_PREFIX;

    const container = this.markup.createNode({
      attributes: { class"highlighter-container" },
    });

    const root = this.markup.createNode({
      parent: container,
      attributes: {
        id: "root",
        class"root",
        hidden: "true",
      },
      prefix,
    });

    const svg = this.markup.createSVGNode({
      nodeType: "svg",
      parent: root,
      attributes: {
        id: "elements",
        class"elements",
        width: "100%",
        height: "100%",
      },
      prefix,
    });

    for (const side of SIDES) {
      this.markup.createSVGNode({
        nodeType: "line",
        parent: svg,
        attributes: {
          class: `guide-${side}`,
          id: `guide-${side}`,
          hidden: "true",
        },
        prefix,
      });
    }

    this.markup.createNode({
      nodeType: "label",
      attributes: {
        id: "label-size",
        class"label-size",
        hidden: "true",
      },
      parent: root,
      prefix,
    });

    this.markup.createNode({
      nodeType: "label",
      attributes: {
        id: "label-position",
        class"label-position",
        hidden: "true",
      },
      parent: root,
      prefix,
    });

    // Creating a <g> element in order to group all the paths below, that
    // together represent the measuring tool; so that would be easier move them
    // around
    const g = this.markup.createSVGNode({
      nodeType: "g",
      attributes: {
        id: "tool",
      },
      parent: svg,
      prefix,
    });

    this.markup.createSVGNode({
      nodeType: "path",
      attributes: {
        id: "box-path",
        class"box-path",
      },
      parent: g,
      prefix,
    });

    this.markup.createSVGNode({
      nodeType: "path",
      attributes: {
        id: "diagonal-path",
        class"diagonal-path",
      },
      parent: g,
      prefix,
    });

    for (const handler of HANDLERS) {
      this.markup.createSVGNode({
        nodeType: "circle",
        parent: g,
        attributes: {
          class: `handler-${handler}`,
          id: `handler-${handler}`,
          r: HANDLER_SIZE,
          hidden: "true",
        },
        prefix,
      });
    }

    return container;
  }

  _update() {
    const { window } = this.env;

    setIgnoreLayoutChanges(true);

    const zoom = getCurrentZoom(window);

    const { width, height } = getWindowDimensions(window);

    const { rect } = this;

    const isZoomChanged = zoom !== rect.zoom;

    if (isZoomChanged) {
      rect.zoom = zoom;
      this.updateLabel();
    }

    const isDocumentSizeChanged =
      width !== rect.documentWidth || height !== rect.documentHeight;

    if (isDocumentSizeChanged) {
      rect.documentWidth = width;
      rect.documentHeight = height;
    }

    // If either the document's size or the zoom is changed since the last
    // repaint, we update the tool's size as well.
    if (isZoomChanged || isDocumentSizeChanged) {
      this.updateViewport();
    }

    setIgnoreLayoutChanges(false, window.document.documentElement);

    this._rafID = window.requestAnimationFrame(() => this._update());
  }

  _cancelUpdate() {
    if (this._rafID) {
      this.env.window.cancelAnimationFrame(this._rafID);
      this._rafID = 0;
    }
  }

  destroy() {
    this.hide();

    this._cancelUpdate();

    const { pageListenerTarget } = this.env;

    if (pageListenerTarget) {
      DOM_EVENTS.forEach(type =>
        pageListenerTarget.removeEventListener(type, this)
      );
    }

    this.markup.destroy();

    EventEmitter.emit(this"destroy");
  }

  show() {
    setIgnoreLayoutChanges(true);

    this.getElement("root").removeAttribute("hidden");

    this._update();

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
  }

  hide() {
    setIgnoreLayoutChanges(true);

    this.hideLabel(LABEL_TYPE_SIZE);
    this.hideLabel(LABEL_TYPE_POSITION);

    this.getElement("root").setAttribute("hidden""true");

    this._cancelUpdate();

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
  }

  getElement(id) {
    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
  }

  setSize(w, h) {
    this.setRect(undefined, undefined, w, h);
  }

  setRect(x, y, w, h) {
    const { rect } = this;

    if (typeof x !== "undefined") {
      rect.x = x;
    }

    if (typeof y !== "undefined") {
      rect.y = y;
    }

    if (typeof w !== "undefined") {
      rect.w = w;
    }

    if (typeof h !== "undefined") {
      rect.h = h;
    }

    setIgnoreLayoutChanges(true);

    if (this._dragging) {
      this.updatePaths();
      this.updateHandlers();
    }

    this.updateLabel();

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
  }

  updatePaths() {
    const { x, y, w, h } = this.rect;
    const dir = `M0 0 L${w} 0 L${w} ${h} L0 ${h}z`;

    // Adding correction to the line path, otherwise some pixels are drawn
    // outside the main rectangle area.
    const x1 = w > 0 ? 0.5 : 0;
    const y1 = w < 0 && h < 0 ? -0.5 : 0;
    const w1 = w + (h < 0 && w < 0 ? 0.5 : 0);
    const h1 = h + (h > 0 && w > 0 ? -0.5 : 0);

    const linedir = `M${x1} ${y1} L${w1} ${h1}`;

    this.getElement("box-path").setAttribute("d", dir);
    this.getElement("diagonal-path").setAttribute("d", linedir);
    this.getElement("tool").setAttribute("transform", `translate(${x},${y})`);
  }

  updateLabel(type) {
    type = type || (this._dragging ? LABEL_TYPE_SIZE : LABEL_TYPE_POSITION);

    const isSizeLabel = type === LABEL_TYPE_SIZE;

    const label = this.getElement(`label-${type}`);

    let origin = "top left";

    const { innerWidth, innerHeight, scrollX, scrollY } = this.env.window;
    const { x: mouseX, y: mouseY } = this.mouseCoords;
    let { x, y, w, h, zoom } = this.rect;
    const scale = 1 / zoom;

    w = w || 0;
    h = h || 0;
    x = x || 0;
    y = y || 0;
    if (type === LABEL_TYPE_SIZE) {
      x += w;
      y += h;
    } else {
      x = mouseX;
      y = mouseY;
    }

    let labelMargin, labelHeight, labelWidth;

    if (isSizeLabel) {
      labelMargin = LABEL_SIZE_MARGIN;
      labelWidth = LABEL_SIZE_WIDTH;
      labelHeight = LABEL_SIZE_HEIGHT;

      const d = Math.hypot(w, h).toFixed(2);

      label.setTextContent(`W: ${Math.abs(w)} px
                            H: ${Math.abs(h)} px
                            ↘: ${d}px`);
    } else {
      labelMargin = LABEL_POS_MARGIN;
      labelWidth = LABEL_POS_WIDTH;
      labelHeight = LABEL_POS_HEIGHT;

      label.setTextContent(`${mouseX}
                            ${mouseY}`);
    }

    // Size used to position properly the label
    const labelBoxWidth = (labelWidth + labelMargin) * scale;
    const labelBoxHeight = (labelHeight + labelMargin) * scale;

    const isGoingLeft = w < scrollX;
    const isSizeGoingLeft = isSizeLabel && isGoingLeft;
    const isExceedingLeftMargin = x - labelBoxWidth < scrollX;
    const isExceedingRightMargin = x + labelBoxWidth > innerWidth + scrollX;
    const isExceedingTopMargin = y - labelBoxHeight < scrollY;
    const isExceedingBottomMargin = y + labelBoxHeight > innerHeight + scrollY;

    if ((isSizeGoingLeft && !isExceedingLeftMargin) || isExceedingRightMargin) {
      x -= labelBoxWidth;
      origin = "top right";
    } else {
      x += labelMargin * scale;
    }

    if (isSizeLabel) {
      y += isExceedingTopMargin ? labelMargin * scale : -labelBoxHeight;
    } else {
      y += isExceedingBottomMargin ? -labelBoxHeight : labelMargin * scale;
    }

    label.setAttribute(
      "style",
      `
      width: ${labelWidth}px;
      height: ${labelHeight}px;
      transform-origin: ${origin};
      transform: translate(${x}px,${y}px) scale(${scale})
    `
    );

    if (!isSizeLabel) {
      const labelSize = this.getElement("label-size");
      const style = labelSize.getAttribute("style");

      if (style) {
        labelSize.setAttribute(
          "style",
          style.replace(/scale[^)]+\)/, `scale(${scale})`)
        );
      }
    }
  }

  updateViewport() {
    const { devicePixelRatio } = this.env.window;
    const { documentWidth, documentHeight, zoom } = this.rect;

    // Because `devicePixelRatio` is affected by zoom (see bug 809788),
    // in order to get the "real" device pixel ratio, we need divide by `zoom`
    const pixelRatio = devicePixelRatio / zoom;

    // The "real" device pixel ratio is used to calculate the max stroke
    // width we can actually assign: on retina, for instance, it would be 0.5,
    // where on non high dpi monitor would be 1.
    const minWidth = 1 / pixelRatio;
    const strokeWidth = minWidth / zoom;

    this.getElement("root").setAttribute(
      "style",
      `stroke-width:${strokeWidth};
       width:${documentWidth}px;
       height:${documentHeight}px;`
    );
  }

  updateGuides() {
    const { x, y, w, h } = this.rect;

    let guide = this.getElement("guide-top");

    guide.setAttribute("x1""0");
    guide.setAttribute("y1", y);
    guide.setAttribute("x2""100%");
    guide.setAttribute("y2", y);

    guide = this.getElement("guide-right");

    guide.setAttribute("x1", x + w);
    guide.setAttribute("y1", 0);
    guide.setAttribute("x2", x + w);
    guide.setAttribute("y2""100%");

    guide = this.getElement("guide-bottom");

    guide.setAttribute("x1""0");
    guide.setAttribute("y1", y + h);
    guide.setAttribute("x2""100%");
    guide.setAttribute("y2", y + h);

    guide = this.getElement("guide-left");

    guide.setAttribute("x1", x);
    guide.setAttribute("y1", 0);
    guide.setAttribute("x2", x);
    guide.setAttribute("y2""100%");
  }

  setHandlerPosition(handler, x, y) {
    const handlerElement = this.getElement(`handler-${handler}`);
    handlerElement.setAttribute("cx", x);
    handlerElement.setAttribute("cy", y);
  }

  updateHandlers() {
    const { w, h } = this.rect;

    this.setHandlerPosition("top", w / 2, 0);
    this.setHandlerPosition("topright", w, 0);
    this.setHandlerPosition("right", w, h / 2);
    this.setHandlerPosition("bottomright", w, h);
    this.setHandlerPosition("bottom", w / 2, h);
    this.setHandlerPosition("bottomleft", 0, h);
    this.setHandlerPosition("left", 0, h / 2);
    this.setHandlerPosition("topleft", 0, 0);
  }

  showLabel(type) {
    setIgnoreLayoutChanges(true);

    this.getElement(`label-${type}`).removeAttribute("hidden");

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
  }

  hideLabel(type) {
    setIgnoreLayoutChanges(true);

    this.getElement(`label-${type}`).setAttribute("hidden""true");

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
  }

  showGuides() {
    const prefix = this.ID_CLASS_PREFIX + "guide-";

    for (const side of SIDES) {
      this.markup.removeAttributeForElement(`${prefix + side}`, "hidden");
    }
  }

  hideGuides() {
    const prefix = this.ID_CLASS_PREFIX + "guide-";

    for (const side of SIDES) {
      this.markup.setAttributeForElement(`${prefix + side}`, "hidden""true");
    }
  }

  showHandler(id) {
    const prefix = this.ID_CLASS_PREFIX + "handler-";
    this.markup.removeAttributeForElement(prefix + id, "hidden");
  }

  showHandlers() {
    const prefix = this.ID_CLASS_PREFIX + "handler-";

    for (const handler of HANDLERS) {
      this.markup.removeAttributeForElement(prefix + handler, "hidden");
    }
  }

  hideAll() {
    this.hideLabel(LABEL_TYPE_POSITION);
    this.hideLabel(LABEL_TYPE_SIZE);
    this.hideGuides();
    this.hideHandlers();
  }

  showGuidesAndHandlers() {
    // Shows the guides and handlers only if an actual area is selected
    if (this.rect.w !== 0 && this.rect.h !== 0) {
      this.updateGuides();
      this.showGuides();
      this.updateHandlers();
      this.showHandlers();
    }
  }

  hideHandlers() {
    const prefix = this.ID_CLASS_PREFIX + "handler-";

    for (const handler of HANDLERS) {
      this.markup.setAttributeForElement(prefix + handler, "hidden""true");
    }
  }

  handleEvent(event) {
    const { target, type } = event;

    switch (type) {
      case "mousedown":
        if (event.button || this._dragging) {
          return;
        }

        const isHandler = event.originalTarget.id.includes("handler");
        if (isHandler) {
          this.handleResizingMouseDownEvent(event);
        } else {
          this.handleMouseDownEvent(event);
        }
        break;
      case "mousemove":
        if (this._dragging && this._dragging.handler) {
          this.handleResizingMouseMoveEvent(event);
        } else {
          this.handleMouseMoveEvent(event);
        }
        break;
      case "mouseup":
        if (this._dragging) {
          if (this._dragging.handler) {
            this.handleResizingMouseUpEvent();
          } else {
            this.handleMouseUpEvent();
          }
        }
        break;
      case "mouseleave": {
        if (!this._dragging) {
          this.hideLabel(LABEL_TYPE_POSITION);
        }
        break;
      }
      case "scroll": {
        this.hideLabel(LABEL_TYPE_POSITION);
        break;
      }
      case "pagehide": {
        // If a page hide event is triggered for current window's highlighter, hide the
        // highlighter.
        if (target.defaultView === this.env.window) {
          this.destroy();
        }
        break;
      }
      case "keydown": {
        this.handleKeyDown(event);
        break;
      }
      case "keyup": {
        if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) {
          this.getElement("handler-topleft").classList.remove(
            HIGHLIGHTED_HANDLER_CLASSNAME
          );
        }
        break;
      }
    }
  }

  handleMouseDownEvent(event) {
    const { pageX, pageY } = event;
    const { window } = this.env;
    const elementId = `${this.ID_CLASS_PREFIX}tool`;

    setIgnoreLayoutChanges(true);

    this.markup.getElement(elementId).classList.add("dragging");

    this.hideAll();

    setIgnoreLayoutChanges(false, window.document.documentElement);

    // Store all the initial values needed for drag & drop
    this._dragging = {
      handler: null,
      x: pageX,
      y: pageY,
    };

    this.setRect(pageX, pageY, 0, 0);
  }

  handleMouseMoveEvent(event) {
    const { pageX, pageY } = event;
    const { mouseCoords } = this;
    let { x, y, w, h } = this.rect;
    let labelType;

    if (this._dragging) {
      w = pageX - x;
      h = pageY - y;

      this.setRect(x, y, w, h);

      labelType = LABEL_TYPE_SIZE;
    } else {
      mouseCoords.x = pageX;
      mouseCoords.y = pageY;
      this.updateLabel(LABEL_TYPE_POSITION);

      labelType = LABEL_TYPE_POSITION;
    }

    this.showLabel(labelType);
  }

  handleMouseUpEvent() {
    setIgnoreLayoutChanges(true);

    this.getElement("tool").classList.remove("dragging");

    this.showGuidesAndHandlers();

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
    this._dragging = null;
  }

  handleResizingMouseDownEvent(event) {
    const { originalTarget, pageX, pageY } = event;
    const { window } = this.env;
    const prefix = this.ID_CLASS_PREFIX + "handler-";
    const handler = originalTarget.id.replace(prefix, "");

    setIgnoreLayoutChanges(true);

    this.markup.getElement(originalTarget.id).classList.add("dragging");

    this.hideAll();
    this.showHandler(handler);

    // Set coordinates to the current measurement area's position
    const [, x, y] = this.getElement("tool")
      .getAttribute("transform")
      .match(/(\d+),(\d+)/);
    this.setRect(Number(x), Number(y));

    setIgnoreLayoutChanges(false, window.document.documentElement);

    // Store all the initial values needed for drag & drop
    this._dragging = {
      handler,
      x: pageX,
      y: pageY,
    };
  }

  handleResizingMouseMoveEvent(event) {
    const { pageX, pageY } = event;
    const { rect } = this;
    let { x, y, w, h } = rect;

    const { handler } = this._dragging;

    switch (handler) {
      case "top":
        y = pageY;
        h = rect.y + rect.h - pageY;
        break;
      case "topright":
        y = pageY;
        w = pageX - rect.x;
        h = rect.y + rect.h - pageY;
        break;
      case "right":
        w = pageX - rect.x;
        break;
      case "bottomright":
        w = pageX - rect.x;
        h = pageY - rect.y;
        break;
      case "bottom":
        h = pageY - rect.y;
        break;
      case "bottomleft":
        x = pageX;
        w = rect.x + rect.w - pageX;
        h = pageY - rect.y;
        break;
      case "left":
        x = pageX;
        w = rect.x + rect.w - pageX;
        break;
      case "topleft":
        x = pageX;
        y = pageY;
        w = rect.x + rect.w - pageX;
        h = rect.y + rect.h - pageY;
        break;
    }

    this.setRect(x, y, w, h);

    // Changes the resizing cursors in case the measuring box is mirrored
    const isMirrored =
      (rect.w < 0 || rect.h < 0) && !(rect.w < 0 && rect.h < 0);
    this.getElement("tool").classList.toggle("mirrored", isMirrored);

    this.showLabel("size");
  }

  handleResizingMouseUpEvent() {
    const { handler } = this._dragging;

    setIgnoreLayoutChanges(true);

    this.getElement(`handler-${handler}`).classList.remove("dragging");
    this.showHandlers();

    this.showGuidesAndHandlers();

    setIgnoreLayoutChanges(falsethis.env.window.document.documentElement);
    this._dragging = null;
  }

  handleKeyDown(event) {
    if (MeasuringToolHighlighter.#isResizeModifierPressed(event)) {
      this.getElement("handler-topleft").classList.add(
        HIGHLIGHTED_HANDLER_CLASSNAME
      );
    }

    if (
      !["ArrowUp""ArrowDown""ArrowLeft""ArrowRight"].includes(event.key)
    ) {
      return;
    }

    const { x, y, w, h } = this.rect;
    const modifier = event.shiftKey ? 10 : 1;

    event.preventDefault();
    if (MeasuringToolHighlighter.#isResizeModifierHeld(event)) {
      // If Ctrl (or Command on OS X) is held, resize the tool
      switch (event.key) {
        case "ArrowUp":
          this.setSize(undefined, h - modifier);
          break;
        case "ArrowDown":
          this.setSize(undefined, h + modifier);
          break;
        case "ArrowLeft":
          this.setSize(w - modifier, undefined);
          break;
        case "ArrowRight":
          this.setSize(w + modifier, undefined);
          break;
      }
    } else {
      // Arrow keys with no modifier move the tool
      switch (event.key) {
        case "ArrowUp":
          this.setRect(undefined, y - modifier);
          break;
        case "ArrowDown":
          this.setRect(undefined, y + modifier);
          break;
        case "ArrowLeft":
          this.setRect(x - modifier, undefined);
          break;
        case "ArrowRight":
          this.setRect(x + modifier, undefined);
          break;
      }
    }

    this.updatePaths();
    this.updateGuides();
    this.updateHandlers();
    this.updateLabel(LABEL_TYPE_SIZE);
  }

  static #isResizeModifierPressed(event) {
    return (
      (!IS_OSX && event.key === "Control") || (IS_OSX && event.key === "Meta")
    );
  }

  static #isResizeModifierHeld(event) {
    return (!IS_OSX && event.ctrlKey) || (IS_OSX && event.metaKey);
  }
}
exports.MeasuringToolHighlighter = MeasuringToolHighlighter;

Messung V0.5
C=90 H=89 G=89

¤ Dauer der Verarbeitung: 0.14 Sekunden  (vorverarbeitet)  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.