0
Fork 0
mirror of https://github.com/penpot/penpot.git synced 2025-02-08 08:09:14 -05:00

🐛 Fix problem with inconsistency with border-radius

This commit is contained in:
alonso.torres 2022-03-18 16:16:33 +01:00
parent 928128ba2d
commit e6f8269c0b
5 changed files with 124 additions and 78 deletions

View file

@ -11,6 +11,9 @@
## 1.13.0-beta
### :boom: Breaking changes
- We've changed the behaviour of the border-radius so it works as CSS that [has some limits](https://www.w3.org/TR/css-backgrounds-3/#corner-overlap).
### :sparkles: New features
- Exporting big files flow [Taiga #2218](https://tree.taiga.io/project/penpot/us/2218)
@ -49,6 +52,7 @@
- Fix problem when importing a SVG with text [#1532](https://github.com/penpot/penpot/issues/1532)
- Fix problem when adding shadows to imported text [#Taiga 3057](https://tree.taiga.io/project/penpot/issue/3057)
- Fix problem when importing SVG's with uses with overriding properties [#Taiga 2884](https://tree.taiga.io/project/penpot/issue/2884)
- Fix inconsistency with radius in SVG an CSS [#1587](https://github.com/penpot/penpot/issues/1587)
### :arrow_up: Deps updates
### :heart: Community contributions by (Thank you!)

View file

@ -11,6 +11,7 @@
[app.common.geom.shapes.bool :as gsb]
[app.common.geom.shapes.common :as gco]
[app.common.geom.shapes.constraints :as gct]
[app.common.geom.shapes.corners :as gsc]
[app.common.geom.shapes.intersect :as gin]
[app.common.geom.shapes.path :as gsp]
[app.common.geom.shapes.rect :as gpr]
@ -153,3 +154,7 @@
;; Constraints
(dm/export gct/default-constraints-h)
(dm/export gct/default-constraints-v)
;; Corners
(dm/export gsc/shape-corners-1)
(dm/export gsc/shape-corners-4)

View file

@ -0,0 +1,46 @@
;; 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/.
;;
;; Copyright (c) UXBOX Labs SL
(ns app.common.geom.shapes.corners)
(defn fix-radius
;; https://www.w3.org/TR/css-backgrounds-3/#corner-overlap
;;
;; > Corner curves must not overlap: When the sum of any two adjacent border radii exceeds the size of the border box,
;; > UAs must proportionally reduce the used values of all border radii until none of them overlap.
;;
;; > The algorithm for reducing radii is as follows: Let f = min(Li/Si), where i ∈ {top, right, bottom, left}, Si is
;; > the sum of the two corresponding radii of the corners on side i, and Ltop = Lbottom = the width of the box, and
;; > Lleft = Lright = the height of the box. If f < 1, then all corner radii are reduced by multiplying them by f.
([width height r]
(let [f (min (/ width (* 2 r))
(/ height (* 2 r)))]
(if (< f 1)
(* r f)
r)))
([width height r1 r2 r3 r4]
(let [f (min (/ width (+ r1 r2))
(/ height (+ r2 r3))
(/ width (+ r3 r4))
(/ height (+ r4 r1)))]
(if (< f 1)
[(* r1 f) (* r2 f) (* r3 f) (* r4 f)]
[r1 r2 r3 r4]))))
(defn shape-corners-1
"Retrieve the effective value for the corner given a single value for corner."
[{:keys [width height rx] :as shape}]
(if (some? rx)
(fix-radius width height rx)
0))
(defn shape-corners-4
"Retrieve the effective value for the corner given four values for the corners."
[{:keys [width height r1 r2 r3 r4]}]
(if (and (some? r1) (some? r2) (some? r3) (some? r4))
(fix-radius width height r1 r2 r3 r4)
[r1 r2 r3 r4]))

View file

@ -11,9 +11,11 @@
[app.common.geom.matrix :as gmt]
[app.common.geom.point :as gpt]
[app.common.geom.shapes.common :as gsc]
[app.common.geom.shapes.corners :as gso]
[app.common.geom.shapes.path :as gsp]
[app.common.path.bool :as pb]
[app.common.path.commands :as pc]))
[app.common.path.commands :as pc]
[app.common.spec.radius :as ctr]))
(def ^:const bezier-circle-c 0.551915024494)
@ -93,7 +95,7 @@
(defn circle->path
"Creates the bezier curves to approximate a circle shape"
[x y width height]
[{:keys [x y width height]}]
(let [mx (+ x (/ width 2))
my (+ y (/ height 2))
ex (+ x width)
@ -116,35 +118,50 @@
(pc/make-curve-to p4 (assoc p3 :x c1x) (assoc p4 :y c2y))
(pc/make-curve-to p1 (assoc p4 :y c1y) (assoc p1 :x c1x))]))
(defn draw-rounded-rect-path
([x y width height r]
(draw-rounded-rect-path x y width height r r r r))
([x y width height r1 r2 r3 r4]
(let [p1 (gpt/point x (+ y r1))
p2 (gpt/point (+ x r1) y)
p3 (gpt/point (+ width x (- r2)) y)
p4 (gpt/point (+ width x) (+ y r2))
p5 (gpt/point (+ width x) (+ height y (- r3)))
p6 (gpt/point (+ width x (- r3)) (+ height y))
p7 (gpt/point (+ x r4) (+ height y))
p8 (gpt/point x (+ height y (- r4)))]
(-> []
(conj (pc/make-move-to p1))
(cond-> (not= p1 p2)
(conj (make-corner-arc p1 p2 :top-left r1)))
(conj (pc/make-line-to p3))
(cond-> (not= p3 p4)
(conj (make-corner-arc p3 p4 :top-right r2)))
(conj (pc/make-line-to p5))
(cond-> (not= p5 p6)
(conj (make-corner-arc p5 p6 :bottom-right r3)))
(conj (pc/make-line-to p7))
(cond-> (not= p7 p8)
(conj (make-corner-arc p7 p8 :bottom-left r4)))
(conj (pc/make-line-to p1))))))
(defn rect->path
"Creates a bezier curve that approximates a rounded corner rectangle"
[x y width height r1 r2 r3 r4 rx]
(let [[r1 r2 r3 r4] (->> [r1 r2 r3 r4] (mapv #(or % rx 0)))
p1 (gpt/point x (+ y r1))
p2 (gpt/point (+ x r1) y)
[{:keys [x y width height] :as shape}]
(case (ctr/radius-mode shape)
:radius-1
(let [radius (gso/shape-corners-1 shape)]
(draw-rounded-rect-path x y width height radius))
p3 (gpt/point (+ width x (- r2)) y)
p4 (gpt/point (+ width x) (+ y r2))
:radius-4
(let [[r1 r2 r3 r4] (gso/shape-corners-4 shape)]
(draw-rounded-rect-path x y width height r1 r2 r3 r4))
p5 (gpt/point (+ width x) (+ height y (- r3)))
p6 (gpt/point (+ width x (- r3)) (+ height y))
p7 (gpt/point (+ x r4) (+ height y))
p8 (gpt/point x (+ height y (- r4)))]
(-> []
(conj (pc/make-move-to p1))
(cond-> (not= p1 p2)
(conj (make-corner-arc p1 p2 :top-left r1)))
(conj (pc/make-line-to p3))
(cond-> (not= p3 p4)
(conj (make-corner-arc p3 p4 :top-right r2)))
(conj (pc/make-line-to p5))
(cond-> (not= p5 p6)
(conj (make-corner-arc p5 p6 :bottom-right r3)))
(conj (pc/make-line-to p7))
(cond-> (not= p7 p8)
(conj (make-corner-arc p7 p8 :bottom-left r4)))
(conj (pc/make-line-to p1)))))
[]))
(declare convert-to-path)
@ -192,9 +209,9 @@
"Transforms the given shape to a path"
([shape]
(convert-to-path shape {}))
([{:keys [type x y width height r1 r2 r3 r4 rx metadata] :as shape} objects]
([{:keys [type metadata] :as shape} objects]
(assert (map? objects))
(case (:type shape)
(case type
:group
(group-to-path shape objects)
@ -204,8 +221,8 @@
(:rect :circle :image :text)
(let [new-content
(case type
:circle (circle->path x y width height)
#_:else (rect->path x y width height r1 r2 r3 r4 rx))
:circle (circle->path shape)
#_:else (rect->path shape))
;; Apply the transforms that had the shape
transform (:transform shape)

View file

@ -7,6 +7,8 @@
(ns app.main.ui.shapes.attrs
(:require
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.geom.shapes :as gsh]
[app.common.spec.radius :as ctr]
[app.common.spec.shape :refer [stroke-caps-line stroke-caps-marker]]
[app.main.ui.context :as muc]
@ -26,58 +28,30 @@
(->> values (map #(+ % width)) (str/join ","))))
(defn- truncate-side
[shape ra-attr rb-attr dimension-attr]
(let [ra (ra-attr shape)
rb (rb-attr shape)
dimension (dimension-attr shape)]
(if (<= (+ ra rb) dimension)
[ra rb]
[(/ (* ra dimension) (+ ra rb))
(/ (* rb dimension) (+ ra rb))])))
(defn- truncate-radius
[shape]
(let [[r-top-left r-top-right]
(truncate-side shape :r1 :r2 :width)
[r-right-top r-right-bottom]
(truncate-side shape :r2 :r3 :height)
[r-bottom-right r-bottom-left]
(truncate-side shape :r3 :r4 :width)
[r-left-bottom r-left-top]
(truncate-side shape :r4 :r1 :height)]
[(min r-top-left r-left-top)
(min r-top-right r-right-top)
(min r-right-bottom r-bottom-right)
(min r-bottom-left r-left-bottom)]))
(defn add-border-radius [attrs shape]
(defn add-border-radius [attrs {:keys [x y width height] :as shape}]
(case (ctr/radius-mode shape)
:radius-1
(obj/merge! attrs #js {:rx (:rx shape 0)
:ry (:ry shape 0)})
(let [radius (gsh/shape-corners-1 shape)]
(obj/merge! attrs #js {:rx radius :ry radius}))
:radius-4
(let [[r1 r2 r3 r4] (truncate-radius shape)
top (- (:width shape) r1 r2)
right (- (:height shape) r2 r3)
bottom (- (:width shape) r3 r4)
left (- (:height shape) r4 r1)]
(obj/merge! attrs #js {:d (str "M" (+ (:x shape) r1) "," (:y shape) " "
"h" top " "
"a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
"v" right " "
"a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
"h" (- bottom) " "
"a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
"v" (- left) " "
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
"z")}))
(let [[r1 r2 r3 r4] (gsh/shape-corners-4 shape)
top (- width r1 r2)
right (- height r2 r3)
bottom (- width r3 r4)
left (- height r4 r1)]
(obj/merge! attrs #js {:d (dm/str
"M" (+ x r1) "," y " "
"h" top " "
"a" r2 "," r2 " 0 0 1 " r2 "," r2 " "
"v" right " "
"a" r3 "," r3 " 0 0 1 " (- r3) "," r3 " "
"h" (- bottom) " "
"a" r4 "," r4 " 0 0 1 " (- r4) "," (- r4) " "
"v" (- left) " "
"a" r1 "," r1 " 0 0 1 " r1 "," (- r1) " "
"z")}))
attrs))
(defn add-fill