From 9d2f484aa36301a31ba6013b0261a0d1acdec037 Mon Sep 17 00:00:00 2001
From: "alonso.torres" <alonso.torres@kaleidos.net>
Date: Mon, 14 Oct 2024 11:44:29 +0200
Subject: [PATCH] :bug: Fix problem with horizontal/vertical lines and shadows

---
 CHANGES.md                                   |  1 +
 frontend/src/app/main/ui/shapes/filters.cljs | 39 +++++++++++++++++---
 2 files changed, 35 insertions(+), 5 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 7363d47ee..46e567bbf 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -45,6 +45,7 @@
 - Add visual feedback when moving an element into a board [Github #3210](https://github.com/penpot/penpot/issues/3210)
 - Fix percent calculation on grid layout tracks [Github #4688](https://github.com/penpot/penpot/issues/4688)
 - Fix problem with caps and inner shadows [Github #4517](https://github.com/penpot/penpot/issues/4517)
+- Fix problem with horizontal/vertical lines and shadows [Github #4516](https://github.com/penpot/penpot/issues/4516)
 
 ## 2.2.1
 
diff --git a/frontend/src/app/main/ui/shapes/filters.cljs b/frontend/src/app/main/ui/shapes/filters.cljs
index f7ff505db..5d23c26ab 100644
--- a/frontend/src/app/main/ui/shapes/filters.cljs
+++ b/frontend/src/app/main/ui/shapes/filters.cljs
@@ -10,6 +10,7 @@
    [app.common.data :as d]
    [app.common.data.macros :as dm]
    [app.common.geom.shapes.bounds :as gsb]
+   [app.common.math :as mth]
    [app.common.uuid :as uuid]
    [cuerdas.core :as str]
    [rumext.v2 :as mf]))
@@ -129,6 +130,34 @@
   [filters]
   (map #(assoc %1 :filter-in %2) filters (cons nil (map :id filters))))
 
+(defn filter-coords
+  [bounds selrect padding]
+  (if (or (mth/close? 0.01 (:width selrect))
+          (mth/close? 0.01 (:height selrect)))
+
+    ;; We cannot use "objectBoundingbox" if the shape doesn't have width/heigth
+    ;; From the SVG spec (https://www.w3.org/TR/SVG11/coords.html#ObjectBoundingBox
+    ;; Keyword objectBoundingBox should not be used when the geometry of the applicable element
+    ;; has no width or no height, such as the case of a horizontal or vertical line, even when
+    ;; the line has actual thickness when viewed due to having a non-zero stroke width since
+    ;; stroke width is ignored for bounding box calculations. When the geometry of the
+    ;; applicable element has no width or height and objectBoundingBox is specified, then
+    ;; the given effect (e.g., a gradient or a filter) will be ignored.
+    (let [filter-width  (+ (:width bounds) (* 2 (:horizontal padding)))
+          filter-height (+ (:height bounds) (* 2 (:vertical padding)))
+          filter-x      (- (:x bounds) #_(:x selrect) (:horizontal padding))
+          filter-y      (- (:y bounds) #_(:y selrect) (:vertical padding))
+          filter-units  "userSpaceOnUse"]
+      [filter-x filter-y filter-width filter-height filter-units])
+
+    ;; If the width/height is not zero we use objectBoundingBox as it's more stable
+    (let [filter-width  (/ (+ (:width bounds) (* 2 (:horizontal padding))) (:width selrect))
+          filter-height (/ (+ (:height bounds) (* 2 (:vertical padding))) (:height selrect))
+          filter-x      (/ (- (:x bounds) (:x selrect) (:horizontal padding)) (:width selrect))
+          filter-y      (/ (- (:y bounds) (:y selrect) (:vertical padding)) (:height selrect))
+          filter-units  "objectBoundingBox"]
+      [filter-x filter-y filter-width filter-height filter-units])))
+
 (mf/defc filters
   [{:keys [filter-id shape]}]
 
@@ -136,17 +165,17 @@
         bounds        (gsb/get-rect-filter-bounds (:selrect shape) filters (or (-> shape :blur :value) 0))
         padding       (gsb/calculate-padding shape)
         selrect       (:selrect shape)
-        filter-x      (/ (- (:x bounds) (:x selrect) (:horizontal padding)) (:width selrect))
-        filter-y      (/ (- (:y bounds) (:y selrect) (:vertical padding)) (:height selrect))
-        filter-width  (/ (+ (:width bounds) (* 2 (:horizontal padding))) (:width selrect))
-        filter-height (/ (+ (:height bounds) (* 2 (:vertical padding))) (:height selrect))]
+
+        [filter-x filter-y filter-width filter-height filter-units]
+        (filter-coords bounds selrect padding)]
+
     (when (> (count filters) 2)
       [:filter {:id          filter-id
                 :x           filter-x
                 :y           filter-y
                 :width       filter-width
                 :height      filter-height
-                :filterUnits "objectBoundingBox"
+                :filterUnits filter-units
                 :color-interpolation-filters "sRGB"}
        (for [[index entry] (d/enumerate filters)]
          [:& filter-entry {:key (dm/str filter-id "-" index)