Bug 1488673 - part5 : find the best position for the cue box in order not to overlap it with other cue boxes. r=heycam
authorAlastor Wu <alwu@mozilla.com>
Tue, 19 Mar 2019 03:30:09 +0000
changeset 464985 a1841a4aa3f1188b9ecd9666dcbdab58e1258778
parent 464984 9e2f80f5ec79e1d33826065ef40fb799da3ad574
child 464986 3f6a9b725f218a15e79d87f4106b6ac20a1182ef
push id112486
push useropoprus@mozilla.com
push dateTue, 19 Mar 2019 16:41:04 +0000
treeherdermozilla-inbound@ee866fb50236 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1488673
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1488673 - part5 : find the best position for the cue box in order not to overlap it with other cue boxes. r=heycam According to the spec 7.2.10 [1], the step13 to step21 (snap-to-line is true) and the step3 to step5 (snap-to-line is false), we need to find the best position where the cue box has least overlap with other cue boxes. In addition, in order not to be affected by CSS transformation, use non-tranformed attribute in `BoxPosition`. [1] https://w3c.github.io/webvtt/#ref-for-webvtt-cue-snap-to-lines-flag-12 https://w3c.github.io/webvtt/#ref-for-webvtt-cue-snap-to-lines-flag-13 Differential Revision: https://phabricator.services.mozilla.com/D22578
dom/media/webvtt/vtt.jsm
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -893,17 +893,21 @@ XPCOMUtils.defineLazyPreferenceGetter(th
         left: this.left - reference.left,
         right: reference.right - this.right,
         height: this.height,
         width: this.width
       };
     }
   }
 
-  function adjustBoxPosition(styleBox, containerBox, controlBarBox) {
+  BoxPosition.prototype.clone = function(){
+    return new BoxPosition(this);
+  };
+
+  function adjustBoxPosition(styleBox, containerBox, controlBarBox, outputBoxes) {
     const cue = styleBox.cue;
     const isWritingDirectionHorizontal = cue.vertical == "";
     let box = new BoxPosition(styleBox);
 
     // Spec 7.2.10, adjust the positions of boxes according to the appropriate
     // steps from the following list. Also, we use offsetHeight/offsetWidth here
     // in order to prevent the incorrect positioning caused by CSS transform
     // scale.
@@ -915,92 +919,146 @@ XPCOMUtils.defineLazyPreferenceGetter(th
       // width or height of the box would be changed when the text is wrapped to
       // different line. Ex. if text is wrapped to two line, the height or width
       // of the box would become 2 times of font size.
       let step = parseFloat(styleBox.fontSize.replace("px", ""));
       if (step == 0) {
         return;
       }
 
+      // spec 7.2.10.4 ~ 7.2.10.6
       let line = Math.floor(cue.computedLine + 0.5);
       if (cue.vertical == "rl") {
         line = -1 * (line + 1);
       }
 
+      // spec 7.2.10.7 ~ 7.2.10.8
       let position = step * line;
       if (cue.vertical == "rl") {
         position = position - box.width + step;
       }
 
+      // spec 7.2.10.9
       if (line < 0) {
         position += fullDimension;
         step = -1 * step;
       }
 
-      if (isWritingDirectionHorizontal) {
-        box.top += position;
-      } else {
-        box.left += position;
+      // spec 7.2.10.10, move the box to the specific position along the direction.
+      const movingDirection = isWritingDirectionHorizontal ? "+y" : "+x";
+      box.move(movingDirection, position);
+
+      // spec 7.2.10.11, remember the position as specified position.
+      let specifiedPosition = box.clone();
+
+      // spec 7.2.10.12, let title area be a box that covers all of the video’s
+      // rendering area.
+      const titleAreaBox = containerBox.clone();
+      if (controlBarBox) {
+        titleAreaBox.height -= controlBarBox.height;
       }
 
       function isBoxOutsideTheRenderingArea() {
         if (isWritingDirectionHorizontal) {
           // the top side of the box is above the rendering area, or the bottom
           // side of the box is below the rendering area.
           return step < 0 && box.top < 0 ||
                  step > 0 && box.bottom > fullDimension;
         }
         // the left side of the box is outside the left side of the rendering
         // area, or the right side of the box is outside the right side of the
         // rendering area.
         return step < 0 && box.left < 0 ||
                step > 0 && box.right > fullDimension;
       }
 
-      // TODO : implement checking if current box is overlapping with any other
-      // boxes in output. 7.2.10.13
+      // spec 7.2.10.13, if none of the boxes in boxes would overlap any of the
+      // boxes in output, and all of the boxes in boxes are entirely within the
+      // title area box.
+      let switched = false;
+      while (!box.within(titleAreaBox) || box.overlapsAny(outputBoxes)) {
+        // spec 7.2.10.14, check if we need to switch the direction.
+        if (isBoxOutsideTheRenderingArea()) {
+          // spec 7.2.10.17, if `switched` is true, remove all the boxes in
+          // `boxes`, which means we shouldn't apply any CSS boxes for this cue.
+          // Therefore, returns null box.
+          if (switched) {
+            return null;
+          }
+          // spec 7.2.10.18 ~ 7.2.10.20
+          switched = true;
+          box = specifiedPosition.clone();
+          step = -1 * step;
+        }
+        // spec 7.2.10.15, moving box along the specific direction.
+        box.move(movingDirection, step);
+      }
 
-      // 7.2.10.14 ~ 7.2.10.21, if the box is outside the rendering area, we
-      // should switch the direction.
-      if (isBoxOutsideTheRenderingArea()) {
-        step = -1 * step;
+      if (isWritingDirectionHorizontal) {
+        styleBox.applyStyles({
+          top: getPercentagePosition(box.top, fullDimension),
+        });
+      } else {
+        styleBox.applyStyles({
+          left: getPercentagePosition(box.left, fullDimension),
+        });
+      }
+    } else {
+      // (snap-to-lines if false) spec 7.2.10.1 ~ 7.2.10.2
+      if (cue.lineAlign != "start") {
+        const isCenterAlign = cue.lineAlign == "center";
+        const movingDirection = isWritingDirectionHorizontal ? "-y" : "-x";
         if (isWritingDirectionHorizontal) {
-          box.top += step;
+          box.move(movingDirection, isCenterAlign ? box.height : box.height / 2);
         } else {
-          box.left += step;
+          box.move(movingDirection, isCenterAlign ? box.width : box.width / 2);
         }
       }
-    } else {
-      if (cue.lineAlign != "start") {
-        const isCenterAlign = cue.lineAlign == "center";
-        if (isWritingDirectionHorizontal) {
-          box.top += isCenterAlign ? box.height : box.height / 2;
-        } else {
-          box.left += isCenterAlign ? box.width : box.width / 2;
+
+      // spec 7.2.10.3
+      let bestPosition = {},
+          specifiedPosition = box.clone(),
+          outsideAreaPercentage = 1; // Highest possible so the first thing we get is better.
+      let hasFoundBestPosition = false;
+      const axis = ["-y", "-x", "+x", "+y"];
+      const toMove = parseFloat(styleBox.fontSize.replace("px", ""));
+      for (let i = 0; i < axis.length && !hasFoundBestPosition; i++) {
+        while (box.overlapsOppositeAxis(containerBox, axis[i]) ||
+               (!box.within(containerBox) || box.overlapsAny(outputBoxes))) {
+          box.move(axis[i], toMove);
         }
+        // We found a spot where we aren't overlapping anything. This is our
+        // best position.
+        if (box.within(containerBox)) {
+          bestPosition = box.clone();
+          hasFoundBestPosition = true;
+          break;
+        }
+        let p = box.intersectPercentage(containerBox);
+        // If we're outside the container box less then we were on our last try
+        // then remember this position as the best position.
+        if (outsideAreaPercentage > p) {
+          bestPosition = box.clone();
+          outsideAreaPercentage = p;
+        }
+        // Reset the box position to the specified position.
+        box = specifiedPosition.clone();
       }
-      // TODO : implement finding the best position, 7.2.10.4
+
+      styleBox.applyStyles({
+        top: getPercentagePosition(box.top, fullDimension),
+        left: getPercentagePosition(box.left, fullDimension),
+      });
     }
 
     // In order to not be affected by CSS scale, so we use '%' to make sure the
     // cue can stick in the right position.
     function getPercentagePosition(position, fullDimension) {
       return (position / fullDimension) * 100 + "%";
     }
-    if (isWritingDirectionHorizontal) {
-      // Avoid to overlap the cue with the video control.
-      const controlOffset = controlBarBox ? controlBarBox.height : 0;
-      styleBox.applyStyles({
-        top: getPercentagePosition(box.top - controlOffset, fullDimension),
-      });
-    } else {
-      styleBox.applyStyles({
-        left: getPercentagePosition(box.left, fullDimension),
-      });
-    }
 
     return box;
   }
 
   // Move a StyleBox to its specified, or next best, position. The containerBox
   // is the box that contains the StyleBox, such as a div. boxPositions are
   // a list of other boxes that the styleBox can't overlap with.
   function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
@@ -1265,24 +1323,26 @@ XPCOMUtils.defineLazyPreferenceGetter(th
           rootOfCues.appendChild(currentRegionNodeDiv);
           cue.displayState = styleBox.div;
           boxPositions.push(new BoxPosition(currentRegionBox));
         } else {
           // Compute the intial position and styles of the cue div.
           styleBox = new CueStyleBox(window, cue, containerBox);
           rootOfCues.appendChild(styleBox.div);
 
-          // Move the cue to correct position.
-          let cueBox = adjustBoxPosition(styleBox, containerBox, controlBarBox);
-
-          // Remember the computed div so that we don't have to recompute it later
-          // if we don't have too.
-          cue.displayState = styleBox.div;
-
-          boxPositions.push(cueBox);
+          // Move the cue to correct position, we might get the null box if the
+          // result of algorithm doesn't want us to show the cue when we don't
+          // have any room for this cue.
+          let cueBox = adjustBoxPosition(styleBox, containerBox, controlBarBox, boxPositions);
+          if (cueBox) {
+            // Remember the computed div so that we don't have to recompute it later
+            // if we don't have too.
+            cue.displayState = styleBox.div;
+            boxPositions.push(cueBox);
+          }
         }
       }
     })();
   };
 
   WebVTT.Parser = function(window, decoder) {
     this.window = window;
     this.state = "INITIAL";