mirror of
https://github.com/penpot/penpot.git
synced 2025-05-17 02:26:14 +02:00
commit
33882f44ef
12 changed files with 143 additions and 74 deletions
|
@ -25,6 +25,7 @@
|
||||||
:content :content-group
|
:content :content-group
|
||||||
:hidden :visibility-group
|
:hidden :visibility-group
|
||||||
:blocked :modifiable-group
|
:blocked :modifiable-group
|
||||||
|
:grow-type :text-font-group
|
||||||
:font-family :text-font-group
|
:font-family :text-font-group
|
||||||
:font-size :text-font-group
|
:font-size :text-font-group
|
||||||
:font-style :text-font-group
|
:font-style :text-font-group
|
||||||
|
|
|
@ -1428,7 +1428,12 @@
|
||||||
wrapper (gsh/selection-rect selected-objs)
|
wrapper (gsh/selection-rect selected-objs)
|
||||||
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))]
|
orig-pos (gpt/point (:x1 wrapper) (:y1 wrapper))]
|
||||||
(cond
|
(cond
|
||||||
(and (selected-frame? state) (not has-frame?))
|
has-frame?
|
||||||
|
(let [index (cph/get-position-on-parent page-objects uuid/zero)
|
||||||
|
delta (gpt/subtract mouse-pos orig-pos)]
|
||||||
|
[uuid/zero uuid/zero delta index])
|
||||||
|
|
||||||
|
(selected-frame? state)
|
||||||
(let [frame-id (first page-selected)
|
(let [frame-id (first page-selected)
|
||||||
frame-object (get page-objects frame-id)
|
frame-object (get page-objects frame-id)
|
||||||
|
|
||||||
|
|
|
@ -23,9 +23,22 @@
|
||||||
(us/verify ::spec/content new-content)
|
(us/verify ::spec/content new-content)
|
||||||
(let [shape-id (:id shape)
|
(let [shape-id (:id shape)
|
||||||
|
|
||||||
|
[old-points old-selrect]
|
||||||
|
(helpers/content->points+selrect shape old-content)
|
||||||
|
|
||||||
[new-points new-selrect]
|
[new-points new-selrect]
|
||||||
(helpers/content->points+selrect shape new-content)
|
(helpers/content->points+selrect shape new-content)
|
||||||
|
|
||||||
|
;; We set the old values so the update-shapes works
|
||||||
|
objects
|
||||||
|
(-> objects
|
||||||
|
(update
|
||||||
|
shape-id
|
||||||
|
assoc
|
||||||
|
:content old-content
|
||||||
|
:selrect old-selrect
|
||||||
|
:points old-points))
|
||||||
|
|
||||||
changes (-> (pcb/empty-changes it page-id)
|
changes (-> (pcb/empty-changes it page-id)
|
||||||
(pcb/with-objects objects))]
|
(pcb/with-objects objects))]
|
||||||
|
|
||||||
|
|
|
@ -402,7 +402,7 @@
|
||||||
:style {:-webkit-print-color-adjust :exact}
|
:style {:-webkit-print-color-adjust :exact}
|
||||||
:fill "none"}
|
:fill "none"}
|
||||||
|
|
||||||
(let [fonts (ff/frame->fonts object objects)]
|
(let [fonts (ff/shape->fonts object objects)]
|
||||||
[:& ff/fontfaces-style {:fonts fonts}])
|
[:& ff/fontfaces-style {:fonts fonts}])
|
||||||
|
|
||||||
(case (:type object)
|
(case (:type object)
|
||||||
|
|
|
@ -73,12 +73,15 @@
|
||||||
(when (d/not-empty? style)
|
(when (d/not-empty? style)
|
||||||
[:style style])))
|
[:style style])))
|
||||||
|
|
||||||
(defn frame->fonts
|
(defn shape->fonts
|
||||||
[frame objects]
|
[shape objects]
|
||||||
(->> (cph/get-children objects (:id frame))
|
(let [initial (cond-> #{}
|
||||||
(filter cph/text-shape?)
|
(cph/text-shape? shape)
|
||||||
(map (comp fonts/get-content-fonts :content))
|
(into (fonts/get-content-fonts (:content shape))))]
|
||||||
(reduce set/union #{})))
|
(->> (cph/get-children objects (:id shape))
|
||||||
|
(filter cph/text-shape?)
|
||||||
|
(map (comp fonts/get-content-fonts :content))
|
||||||
|
(reduce set/union initial))))
|
||||||
|
|
||||||
(defn shapes->fonts
|
(defn shapes->fonts
|
||||||
[shapes]
|
[shapes]
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
(:require
|
(:require
|
||||||
[app.common.data :as d]
|
[app.common.data :as d]
|
||||||
[app.common.data.macros :as dm]
|
[app.common.data.macros :as dm]
|
||||||
|
[app.common.geom.matrix :as gmt]
|
||||||
|
[app.common.geom.point :as gpt]
|
||||||
[app.common.geom.shapes :as gsh]
|
[app.common.geom.shapes :as gsh]
|
||||||
[app.config :as cfg]
|
[app.config :as cfg]
|
||||||
[app.main.ui.context :as muc]
|
[app.main.ui.context :as muc]
|
||||||
|
@ -30,6 +32,23 @@
|
||||||
(d/update-when :position-data #(mapv update-color %))
|
(d/update-when :position-data #(mapv update-color %))
|
||||||
(assoc :stroke-color "#FFFFFF" :stroke-opacity 1))))
|
(assoc :stroke-color "#FFFFFF" :stroke-opacity 1))))
|
||||||
|
|
||||||
|
(defn position-data-transform
|
||||||
|
[shape {:keys [x y width height]}]
|
||||||
|
(let [rect (gsh/make-rect x (- y height) width height)
|
||||||
|
center (gsh/center-rect rect)]
|
||||||
|
(when (or (:flip-x shape) (:flip-y shape))
|
||||||
|
(-> (gmt/matrix)
|
||||||
|
(gmt/translate center)
|
||||||
|
|
||||||
|
(cond-> (:flip-x shape)
|
||||||
|
(gmt/scale (gpt/point -1 1))
|
||||||
|
|
||||||
|
(:flip-y shape)
|
||||||
|
(gmt/scale (gpt/point 1 -1)))
|
||||||
|
|
||||||
|
(gmt/translate (gpt/negate center))
|
||||||
|
(dm/str)))))
|
||||||
|
|
||||||
(mf/defc text-shape
|
(mf/defc text-shape
|
||||||
{::mf/wrap-props false
|
{::mf/wrap-props false
|
||||||
::mf/wrap [mf/memo]}
|
::mf/wrap [mf/memo]}
|
||||||
|
@ -41,7 +60,7 @@
|
||||||
|
|
||||||
{:keys [x y width height position-data]} shape
|
{:keys [x y width height position-data]} shape
|
||||||
|
|
||||||
transform (str (gsh/transform-matrix shape))
|
transform (str (gsh/transform-matrix shape {:no-flip true}))
|
||||||
|
|
||||||
;; These position attributes are not really necesary but they are convenient for for the export
|
;; These position attributes are not really necesary but they are convenient for for the export
|
||||||
group-props (-> #js {:transform transform
|
group-props (-> #js {:transform transform
|
||||||
|
@ -75,6 +94,7 @@
|
||||||
props (-> #js {:key (dm/str "text-" (:id shape) "-" index)
|
props (-> #js {:key (dm/str "text-" (:id shape) "-" index)
|
||||||
:x (:x data)
|
:x (:x data)
|
||||||
:y y
|
:y y
|
||||||
|
:transform (position-data-transform shape data)
|
||||||
:alignmentBaseline alignment-bl
|
:alignmentBaseline alignment-bl
|
||||||
:dominantBaseline dominant-bl
|
:dominantBaseline dominant-bl
|
||||||
:style (-> #js {:fontFamily (:font-family data)
|
:style (-> #js {:fontFamily (:font-family data)
|
||||||
|
|
|
@ -61,7 +61,7 @@
|
||||||
thumbnail? (unchecked-get props "thumbnail?")
|
thumbnail? (unchecked-get props "thumbnail?")
|
||||||
objects (unchecked-get props "objects")
|
objects (unchecked-get props "objects")
|
||||||
|
|
||||||
fonts (mf/use-memo (mf/deps shape objects) #(ff/frame->fonts shape objects))
|
fonts (mf/use-memo (mf/deps shape objects) #(ff/shape->fonts shape objects))
|
||||||
fonts (-> fonts (hooks/use-equal-memo))
|
fonts (-> fonts (hooks/use-equal-memo))
|
||||||
|
|
||||||
force-render (mf/use-state false)
|
force-render (mf/use-state false)
|
||||||
|
|
|
@ -25,6 +25,9 @@
|
||||||
[app.util.timers :as ts]
|
[app.util.timers :as ts]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
|
(defn strip-position-data [shape]
|
||||||
|
(dissoc shape :position-data :transform :transform-inverse))
|
||||||
|
|
||||||
(defn- update-with-editor-state
|
(defn- update-with-editor-state
|
||||||
"Updates the shape with the current state in the editor"
|
"Updates the shape with the current state in the editor"
|
||||||
[shape editor-state]
|
[shape editor-state]
|
||||||
|
@ -78,26 +81,13 @@
|
||||||
[props]
|
[props]
|
||||||
(let [shape (obj/get props "shape")
|
(let [shape (obj/get props "shape")
|
||||||
on-update (obj/get props "on-update")
|
on-update (obj/get props "on-update")
|
||||||
watch-edits (obj/get props "watch-edits")
|
|
||||||
|
|
||||||
handle-update
|
handle-update
|
||||||
(mf/use-callback
|
(mf/use-callback
|
||||||
(mf/deps shape on-update)
|
(mf/deps shape on-update)
|
||||||
(fn [node]
|
(fn [node]
|
||||||
(when (some? node)
|
(when (some? node)
|
||||||
(on-update shape node))))
|
(on-update shape node))))]
|
||||||
|
|
||||||
text-modifier-ref
|
|
||||||
(mf/use-memo
|
|
||||||
(mf/deps (:id shape))
|
|
||||||
#(refs/workspace-text-modifier-by-id (:id shape)))
|
|
||||||
|
|
||||||
text-modifier
|
|
||||||
(when watch-edits (mf/deref text-modifier-ref))
|
|
||||||
|
|
||||||
shape (cond-> shape
|
|
||||||
(some? text-modifier)
|
|
||||||
(dwt/apply-text-modifier text-modifier))]
|
|
||||||
|
|
||||||
[:& fo/text-shape {:key (str "shape-" (:id shape))
|
[:& fo/text-shape {:key (str "shape-" (:id shape))
|
||||||
:ref handle-update
|
:ref handle-update
|
||||||
|
@ -134,11 +124,6 @@
|
||||||
:on-update handle-update-shape
|
:on-update handle-update-shape
|
||||||
:key (str (dm/str "text-container-" id))}])]))
|
:key (str (dm/str "text-container-" id))}])]))
|
||||||
|
|
||||||
(defn strip-position-data [[id shape]]
|
|
||||||
(let [shape (dissoc shape :position-data :transform :transform-inverse)]
|
|
||||||
[id shape]))
|
|
||||||
|
|
||||||
|
|
||||||
(mf/defc viewport-text-editing
|
(mf/defc viewport-text-editing
|
||||||
{::mf/wrap-props false}
|
{::mf/wrap-props false}
|
||||||
[props]
|
[props]
|
||||||
|
@ -162,7 +147,6 @@
|
||||||
#(st/emit! (dwt/remove-text-modifier (:id shape)))))
|
#(st/emit! (dwt/remove-text-modifier (:id shape)))))
|
||||||
|
|
||||||
[:& text-container {:shape shape
|
[:& text-container {:shape shape
|
||||||
:watch-edits true
|
|
||||||
:on-update handle-update-shape}]))
|
:on-update handle-update-shape}]))
|
||||||
|
|
||||||
(defn check-props
|
(defn check-props
|
||||||
|
@ -178,7 +162,8 @@
|
||||||
edition (obj/get props "edition")
|
edition (obj/get props "edition")
|
||||||
|
|
||||||
xf-texts (comp (filter (comp cph/text-shape? second))
|
xf-texts (comp (filter (comp cph/text-shape? second))
|
||||||
(map strip-position-data))
|
(map (fn [[id shape]]
|
||||||
|
[id (strip-position-data shape)])))
|
||||||
|
|
||||||
text-shapes
|
text-shapes
|
||||||
(mf/use-memo
|
(mf/use-memo
|
||||||
|
@ -198,4 +183,4 @@
|
||||||
[:*
|
[:*
|
||||||
(when editing-shape
|
(when editing-shape
|
||||||
[:& viewport-text-editing {:shape editing-shape}])
|
[:& viewport-text-editing {:shape editing-shape}])
|
||||||
[:& viewport-texts-wrapper {:text-shapes text-shapes}]]))
|
[:& viewport-texts-wrapper {:text-shapes (dissoc text-shapes edition)}]]))
|
||||||
|
|
|
@ -189,7 +189,7 @@
|
||||||
(when (and single? selected?)
|
(when (and single? selected?)
|
||||||
(ts/schedule
|
(ts/schedule
|
||||||
100
|
100
|
||||||
#(dom/scroll-into-view! node #js {:block "center", :behavior "smooth"})))]
|
#(dom/scroll-into-view-if-needed! node #js {:block "center", :behavior "smooth"})))]
|
||||||
|
|
||||||
#(when (some? subid)
|
#(when (some? subid)
|
||||||
(rx/dispose! subid)))))
|
(rx/dispose! subid)))))
|
||||||
|
|
|
@ -297,7 +297,8 @@
|
||||||
|
|
||||||
(when show-text-editor?
|
(when show-text-editor?
|
||||||
[:& text-edition-outline
|
[:& text-edition-outline
|
||||||
{:shape (get base-objects edition)}])
|
{:shape (get base-objects edition)
|
||||||
|
:zoom zoom}])
|
||||||
|
|
||||||
(when show-measures?
|
(when show-measures?
|
||||||
[:& msr/measurement
|
[:& msr/measurement
|
||||||
|
|
66
frontend/src/app/util/text_position_data.js
Normal file
66
frontend/src/app/util/text_position_data.js
Normal file
|
@ -0,0 +1,66 @@
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
goog.provide("app.util.text_position_data");
|
||||||
|
|
||||||
|
goog.scope(function () {
|
||||||
|
const self = app.util.text_position_data;
|
||||||
|
const document = goog.global.document;
|
||||||
|
|
||||||
|
function getRangeRects(node, start, end) {
|
||||||
|
const range = document.createRange();
|
||||||
|
range.setStart(node, start);
|
||||||
|
range.setEnd(node, end);
|
||||||
|
return range.getClientRects();
|
||||||
|
}
|
||||||
|
|
||||||
|
self.parse_text_nodes = function(parent, direction, textNode) {
|
||||||
|
const content = textNode.textContent;
|
||||||
|
const textSize = content.length;
|
||||||
|
const rtl = direction === "rtl";
|
||||||
|
|
||||||
|
let from = 0;
|
||||||
|
let to = 0;
|
||||||
|
let current = "";
|
||||||
|
let result = [];
|
||||||
|
|
||||||
|
while (to < textSize) {
|
||||||
|
const rects = getRangeRects(textNode, from, to + 1);
|
||||||
|
|
||||||
|
if (rects.length > 1) {
|
||||||
|
let position;
|
||||||
|
|
||||||
|
if (rtl) {
|
||||||
|
position = rects[1];
|
||||||
|
} else {
|
||||||
|
position = rects[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push({
|
||||||
|
node: parent,
|
||||||
|
position: position,
|
||||||
|
text: current
|
||||||
|
});
|
||||||
|
|
||||||
|
from = to;
|
||||||
|
current = "";
|
||||||
|
|
||||||
|
} else {
|
||||||
|
current += content[to];
|
||||||
|
to = to + 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// to == textSize
|
||||||
|
const rects = getRangeRects(textNode, from, to);
|
||||||
|
result.push({node: parent, position: rects[0], text: current});
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
});
|
|
@ -11,45 +11,20 @@
|
||||||
[app.common.transit :as transit]
|
[app.common.transit :as transit]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
[app.util.dom :as dom]
|
[app.util.dom :as dom]
|
||||||
[app.util.globals :as global]))
|
[app.util.text-position-data :as tpd]))
|
||||||
|
|
||||||
(defn get-range-rects
|
|
||||||
"Retrieve the rectangles that cover the selection given by a `node` adn
|
|
||||||
the start and end index `start-i`, `end-i`"
|
|
||||||
[^js node start-i end-i]
|
|
||||||
(let [^js range (.createRange global/document)]
|
|
||||||
(.setStart range node start-i)
|
|
||||||
(.setEnd range node end-i)
|
|
||||||
(.getClientRects range)))
|
|
||||||
|
|
||||||
;; TODO: Evaluate to change this function to Javascript
|
|
||||||
(defn parse-text-nodes
|
(defn parse-text-nodes
|
||||||
"Given a text node retrieves the rectangles for everyone of its paragraphs and its text."
|
"Given a text node retrieves the rectangles for everyone of its paragraphs and its text."
|
||||||
[parent-node rtl text-node]
|
[parent-node direction text-node]
|
||||||
|
|
||||||
(let [content (.-textContent text-node)
|
(letfn [(parse-entry [^js entry]
|
||||||
text-size (.-length content)]
|
{:node (.-node entry)
|
||||||
|
:position (dom/bounding-rect->rect (.-position entry))
|
||||||
(loop [from-i 0
|
:text (.-text entry)})]
|
||||||
to-i 0
|
(into
|
||||||
current ""
|
[]
|
||||||
result []]
|
(map parse-entry)
|
||||||
(if (>= to-i text-size)
|
(tpd/parse-text-nodes parent-node direction text-node))))
|
||||||
(let [rects (get-range-rects text-node from-i to-i)
|
|
||||||
entry {:node parent-node
|
|
||||||
:position (dom/bounding-rect->rect (first rects))
|
|
||||||
:text current}]
|
|
||||||
;; We need to add the last element not closed yet
|
|
||||||
(conj result entry))
|
|
||||||
|
|
||||||
(let [rects (get-range-rects text-node from-i (inc to-i))]
|
|
||||||
;; If the rects increase means we're in a new paragraph
|
|
||||||
(if (> (.-length rects) 1)
|
|
||||||
(let [entry {:node parent-node
|
|
||||||
:position (dom/bounding-rect->rect (if rtl (second rects) (first rects)))
|
|
||||||
:text current}]
|
|
||||||
(recur to-i to-i "" (conj result entry)))
|
|
||||||
(recur from-i (inc to-i) (str current (nth content to-i)) result)))))))
|
|
||||||
|
|
||||||
|
|
||||||
(defn calc-text-node-positions
|
(defn calc-text-node-positions
|
||||||
|
@ -87,9 +62,9 @@
|
||||||
(->> text-nodes
|
(->> text-nodes
|
||||||
(mapcat
|
(mapcat
|
||||||
(fn [parent-node]
|
(fn [parent-node]
|
||||||
(let [rtl (= "rtl" (.-dir (.-parentElement parent-node)))]
|
(let [direction (.-direction (js/getComputedStyle parent-node))]
|
||||||
(->> (.-childNodes parent-node)
|
(->> (.-childNodes parent-node)
|
||||||
(mapcat #(parse-text-nodes parent-node rtl %))))))
|
(mapcat #(parse-text-nodes parent-node direction %))))))
|
||||||
(mapv #(update % :position translate-rect))))))
|
(mapv #(update % :position translate-rect))))))
|
||||||
|
|
||||||
(defn calc-position-data
|
(defn calc-position-data
|
||||||
|
@ -102,15 +77,13 @@
|
||||||
(->> text-data
|
(->> text-data
|
||||||
(mapv (fn [{:keys [node position text]}]
|
(mapv (fn [{:keys [node position text]}]
|
||||||
(let [{:keys [x y width height]} position
|
(let [{:keys [x y width height]} position
|
||||||
rtl (= "rtl" (.-dir (.-parentElement ^js node)))
|
|
||||||
styles (js/getComputedStyle ^js node)
|
styles (js/getComputedStyle ^js node)
|
||||||
get (fn [prop]
|
get (fn [prop]
|
||||||
(let [value (.getPropertyValue styles prop)]
|
(let [value (.getPropertyValue styles prop)]
|
||||||
(when (and value (not= value ""))
|
(when (and value (not= value ""))
|
||||||
value)))]
|
value)))]
|
||||||
(d/without-nils
|
(d/without-nils
|
||||||
{:rtl rtl
|
{:x x
|
||||||
:x (if rtl (+ x width) x)
|
|
||||||
:y (+ y height)
|
:y (+ y height)
|
||||||
:width width
|
:width width
|
||||||
:height height
|
:height height
|
||||||
|
@ -123,3 +96,5 @@
|
||||||
:font-style (str (get "font-style"))
|
:font-style (str (get "font-style"))
|
||||||
:fills (transit/decode-str (get "--fills"))
|
:fills (transit/decode-str (get "--fills"))
|
||||||
:text text}))))))))))
|
:text text}))))))))))
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue