mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
✨ Calculate position data in wasm
This commit is contained in:
committed by
Belén Albeza
parent
b705cf953a
commit
ea4d0e1238
@@ -24,6 +24,8 @@
|
|||||||
(def revn-data (atom {}))
|
(def revn-data (atom {}))
|
||||||
(def queue-conj (fnil conj #queue []))
|
(def queue-conj (fnil conj #queue []))
|
||||||
|
|
||||||
|
(def force-persist? #(= % ::force-persist))
|
||||||
|
|
||||||
(defn- update-status
|
(defn- update-status
|
||||||
[status]
|
[status]
|
||||||
(ptk/reify ::update-status
|
(ptk/reify ::update-status
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
[app.main.data.helpers :as dsh]
|
[app.main.data.helpers :as dsh]
|
||||||
[app.main.data.modal :as modal]
|
[app.main.data.modal :as modal]
|
||||||
[app.main.data.notifications :as ntf]
|
[app.main.data.notifications :as ntf]
|
||||||
[app.main.data.persistence :as-alias dps]
|
[app.main.data.persistence :as dps]
|
||||||
[app.main.data.plugins :as dp]
|
[app.main.data.plugins :as dp]
|
||||||
[app.main.data.profile :as du]
|
[app.main.data.profile :as du]
|
||||||
[app.main.data.project :as dpj]
|
[app.main.data.project :as dpj]
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
[app.main.errors]
|
[app.main.errors]
|
||||||
[app.main.features :as features]
|
[app.main.features :as features]
|
||||||
[app.main.features.pointer-map :as fpmap]
|
[app.main.features.pointer-map :as fpmap]
|
||||||
|
[app.main.refs :as refs]
|
||||||
[app.main.repo :as rp]
|
[app.main.repo :as rp]
|
||||||
[app.main.router :as rt]
|
[app.main.router :as rt]
|
||||||
[app.render-wasm :as wasm]
|
[app.render-wasm :as wasm]
|
||||||
@@ -379,6 +380,59 @@
|
|||||||
(->> (rx/from added)
|
(->> (rx/from added)
|
||||||
(rx/map process-wasm-object)))))))
|
(rx/map process-wasm-object)))))))
|
||||||
|
|
||||||
|
(when render-wasm?
|
||||||
|
(let [local-commits-s
|
||||||
|
(->> stream
|
||||||
|
(rx/filter dch/commit?)
|
||||||
|
(rx/map deref)
|
||||||
|
(rx/filter #(and (= :local (:source %))
|
||||||
|
(not (contains? (:tags %) :position-data))))
|
||||||
|
(rx/filter (complement empty?)))
|
||||||
|
|
||||||
|
notifier-s
|
||||||
|
(rx/merge
|
||||||
|
(->> local-commits-s (rx/debounce 1000))
|
||||||
|
(->> stream (rx/filter dps/force-persist?)))
|
||||||
|
|
||||||
|
objects-s
|
||||||
|
(rx/from-atom refs/workspace-page-objects {:emit-current-value? true})
|
||||||
|
|
||||||
|
current-page-id-s
|
||||||
|
(rx/from-atom refs/current-page-id {:emit-current-value? true})]
|
||||||
|
|
||||||
|
(->> local-commits-s
|
||||||
|
(rx/buffer-until notifier-s)
|
||||||
|
(rx/with-latest-from objects-s)
|
||||||
|
(rx/map
|
||||||
|
(fn [[commits objects]]
|
||||||
|
(->> commits
|
||||||
|
(mapcat :redo-changes)
|
||||||
|
(filter #(contains? #{:mod-obj :add-obj} (:type %)))
|
||||||
|
(filter #(cfh/text-shape? objects (:id %)))
|
||||||
|
(map #(vector
|
||||||
|
(:id %)
|
||||||
|
(wasm.api/calculate-position-data (get objects (:id %))))))))
|
||||||
|
|
||||||
|
(rx/with-latest-from current-page-id-s)
|
||||||
|
(rx/map
|
||||||
|
(fn [[text-position-data page-id]]
|
||||||
|
(let [changes
|
||||||
|
(->> text-position-data
|
||||||
|
(mapv (fn [[id position-data]]
|
||||||
|
{:type :mod-obj
|
||||||
|
:id id
|
||||||
|
:page-id page-id
|
||||||
|
:operations
|
||||||
|
[{:type :set
|
||||||
|
:attr :position-data
|
||||||
|
:val position-data
|
||||||
|
:ignore-touched true
|
||||||
|
:ignore-geometry true}]})))]
|
||||||
|
(dch/commit-changes
|
||||||
|
{:redo-changes changes :undo-changes []
|
||||||
|
:save-undo? false
|
||||||
|
:tags #{:position-data}})))))))
|
||||||
|
|
||||||
(->> stream
|
(->> stream
|
||||||
(rx/filter dch/commit?)
|
(rx/filter dch/commit?)
|
||||||
(rx/map deref)
|
(rx/map deref)
|
||||||
|
|||||||
@@ -30,6 +30,9 @@
|
|||||||
(def profile
|
(def profile
|
||||||
(l/derived (l/key :profile) st/state))
|
(l/derived (l/key :profile) st/state))
|
||||||
|
|
||||||
|
(def current-page-id
|
||||||
|
(l/derived (l/key :current-page-id) st/state))
|
||||||
|
|
||||||
(def team
|
(def team
|
||||||
(l/derived (fn [state]
|
(l/derived (fn [state]
|
||||||
(let [team-id (:current-team-id state)
|
(let [team-id (:current-team-id state)
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
[app.common.geom.shapes.points :as gpo]
|
[app.common.geom.shapes.points :as gpo]
|
||||||
[app.common.types.shape.layout :as ctl]
|
[app.common.types.shape.layout :as ctl]
|
||||||
[app.common.uuid :as uuid]
|
[app.common.uuid :as uuid]
|
||||||
|
[app.render-wasm.api :as wasm.api]
|
||||||
[cuerdas.core :as str]
|
[cuerdas.core :as str]
|
||||||
[rumext.v2 :as mf]))
|
[rumext.v2 :as mf]))
|
||||||
|
|
||||||
@@ -275,3 +276,26 @@
|
|||||||
:y2 (:y end-p)
|
:y2 (:y end-p)
|
||||||
:style {:stroke "red"
|
:style {:stroke "red"
|
||||||
:stroke-width (/ 1 zoom)}}]))]))))
|
:stroke-width (/ 1 zoom)}}]))]))))
|
||||||
|
|
||||||
|
(mf/defc debug-text-wasm-position-data
|
||||||
|
{::mf/wrap-props false}
|
||||||
|
[props]
|
||||||
|
(let [zoom (unchecked-get props "zoom")
|
||||||
|
selected-shapes (unchecked-get props "selected-shapes")
|
||||||
|
|
||||||
|
selected-text
|
||||||
|
(when (and (= (count selected-shapes) 1) (= :text (-> selected-shapes first :type)))
|
||||||
|
(first selected-shapes))
|
||||||
|
|
||||||
|
position-data
|
||||||
|
(when selected-text
|
||||||
|
(wasm.api/calculate-position-data selected-text))]
|
||||||
|
|
||||||
|
(for [{:keys [x y width height]} position-data]
|
||||||
|
[:rect {:x x
|
||||||
|
:y (- y height)
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:fill "none"
|
||||||
|
:strokeWidth (/ 1 zoom)
|
||||||
|
:stroke "red"}])))
|
||||||
|
|||||||
@@ -647,6 +647,12 @@
|
|||||||
:hover-top-frame-id @hover-top-frame-id
|
:hover-top-frame-id @hover-top-frame-id
|
||||||
:zoom zoom}])
|
:zoom zoom}])
|
||||||
|
|
||||||
|
(when (dbg/enabled? :text-outline)
|
||||||
|
[:& wvd/debug-text-wasm-position-data
|
||||||
|
{:selected-shapes selected-shapes
|
||||||
|
:objects base-objects
|
||||||
|
:zoom zoom}])
|
||||||
|
|
||||||
(when show-selection-handlers?
|
(when show-selection-handlers?
|
||||||
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
|
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
|
||||||
(when-not text-editing?
|
(when-not text-editing?
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
[app.main.render :as render]
|
[app.main.render :as render]
|
||||||
[app.main.store :as st]
|
[app.main.store :as st]
|
||||||
|
[app.main.ui.shapes.text]
|
||||||
[app.main.worker :as mw]
|
[app.main.worker :as mw]
|
||||||
[app.render-wasm.api.fonts :as f]
|
[app.render-wasm.api.fonts :as f]
|
||||||
[app.render-wasm.api.texts :as t]
|
[app.render-wasm.api.texts :as t]
|
||||||
@@ -1002,10 +1003,7 @@
|
|||||||
(run!
|
(run!
|
||||||
(fn [id]
|
(fn [id]
|
||||||
(f/update-text-layout id)
|
(f/update-text-layout id)
|
||||||
(mw/emit! {:cmd :index/update-text-rect
|
(update-text-rect! id)))))
|
||||||
:page-id (:current-page-id @st/state)
|
|
||||||
:shape-id id
|
|
||||||
:dimensions (get-text-dimensions id)})))))
|
|
||||||
|
|
||||||
(defn process-pending
|
(defn process-pending
|
||||||
([shapes thumbnails full on-complete]
|
([shapes thumbnails full on-complete]
|
||||||
@@ -1361,6 +1359,59 @@
|
|||||||
(h/call wasm/internal-module "_end_temp_objects")
|
(h/call wasm/internal-module "_end_temp_objects")
|
||||||
content)))
|
content)))
|
||||||
|
|
||||||
|
(def POSITION-DATA-U8-SIZE 36)
|
||||||
|
(def POSITION-DATA-U32-SIZE (/ POSITION-DATA-U8-SIZE 4))
|
||||||
|
|
||||||
|
(defn calculate-position-data
|
||||||
|
[shape]
|
||||||
|
(when wasm/context-initialized?
|
||||||
|
(use-shape (:id shape))
|
||||||
|
(let [heapf32 (mem/get-heap-f32)
|
||||||
|
heapu32 (mem/get-heap-u32)
|
||||||
|
offset (-> (h/call wasm/internal-module "_calculate_position_data")
|
||||||
|
(mem/->offset-32))
|
||||||
|
length (aget heapu32 offset)
|
||||||
|
|
||||||
|
max-offset (+ offset 1 (* length POSITION-DATA-U32-SIZE))
|
||||||
|
|
||||||
|
result
|
||||||
|
(loop [result (transient [])
|
||||||
|
offset (inc offset)]
|
||||||
|
(if (< offset max-offset)
|
||||||
|
(let [entry (dr/read-position-data-entry heapu32 heapf32 offset)]
|
||||||
|
(recur (conj! result entry)
|
||||||
|
(+ offset POSITION-DATA-U32-SIZE)))
|
||||||
|
(persistent! result)))
|
||||||
|
|
||||||
|
result
|
||||||
|
(->> result
|
||||||
|
(mapv
|
||||||
|
(fn [{:keys [paragraph span start-pos end-pos direction x y width height]}]
|
||||||
|
(let [content (:content shape)
|
||||||
|
element (-> content :children
|
||||||
|
(get 0) :children ;; paragraph-set
|
||||||
|
(get paragraph) :children ;; paragraph
|
||||||
|
(get span))
|
||||||
|
text (subs (:text element) start-pos end-pos)]
|
||||||
|
|
||||||
|
{:x x
|
||||||
|
:y (+ y height)
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:direction (dr/translate-direction direction)
|
||||||
|
:font-family (get element :font-family)
|
||||||
|
:font-size (get element :font-size)
|
||||||
|
:font-weight (get element :font-weight)
|
||||||
|
:text-transform (get element :text-transform)
|
||||||
|
:text-decoration (get element :text-decoration)
|
||||||
|
:letter-spacing (get element :letter-spacing)
|
||||||
|
:font-style (get element :font-style)
|
||||||
|
:fills (get element :fills)
|
||||||
|
:text text}))))]
|
||||||
|
(mem/free)
|
||||||
|
|
||||||
|
result)))
|
||||||
|
|
||||||
(defn init-wasm-module
|
(defn init-wasm-module
|
||||||
[module]
|
[module]
|
||||||
(let [default-fn (unchecked-get module "default")
|
(let [default-fn (unchecked-get module "default")
|
||||||
|
|||||||
@@ -45,4 +45,29 @@
|
|||||||
:center (gpt/point cx cy)
|
:center (gpt/point cx cy)
|
||||||
:transform (gmt/matrix a b c d e f)}))
|
:transform (gmt/matrix a b c d e f)}))
|
||||||
|
|
||||||
|
(defn read-position-data-entry
|
||||||
|
[heapu32 heapf32 offset]
|
||||||
|
(let [paragraph (aget heapu32 (+ offset 0))
|
||||||
|
span (aget heapu32 (+ offset 1))
|
||||||
|
start-pos (aget heapu32 (+ offset 2))
|
||||||
|
end-pos (aget heapu32 (+ offset 3))
|
||||||
|
x (aget heapf32 (+ offset 4))
|
||||||
|
y (aget heapf32 (+ offset 5))
|
||||||
|
width (aget heapf32 (+ offset 6))
|
||||||
|
height (aget heapf32 (+ offset 7))
|
||||||
|
direction (aget heapu32 (+ offset 8))]
|
||||||
|
{:paragraph paragraph
|
||||||
|
:span span
|
||||||
|
:start-pos start-pos
|
||||||
|
:end-pos end-pos
|
||||||
|
:x x
|
||||||
|
:y y
|
||||||
|
:width width
|
||||||
|
:height height
|
||||||
|
:direction direction}))
|
||||||
|
|
||||||
|
(defn translate-direction
|
||||||
|
[direction]
|
||||||
|
(case direction
|
||||||
|
0 "rtl"
|
||||||
|
"ltr"))
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ mod options;
|
|||||||
mod shadows;
|
mod shadows;
|
||||||
mod strokes;
|
mod strokes;
|
||||||
mod surfaces;
|
mod surfaces;
|
||||||
mod text;
|
pub mod text;
|
||||||
|
|
||||||
mod ui;
|
mod ui;
|
||||||
|
|
||||||
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
use skia_safe::{self as skia, Matrix, RRect, Rect};
|
||||||
|
|||||||
@@ -2,18 +2,15 @@ use super::{filters, RenderState, Shape, SurfaceId};
|
|||||||
use crate::{
|
use crate::{
|
||||||
math::Rect,
|
math::Rect,
|
||||||
shapes::{
|
shapes::{
|
||||||
merge_fills, set_paint_fill, ParagraphBuilderGroup, Stroke, StrokeKind, TextContent,
|
calculate_position_data, calculate_text_layout_data, merge_fills, set_paint_fill,
|
||||||
VerticalAlign,
|
ParagraphBuilderGroup, Stroke, StrokeKind, TextContent,
|
||||||
},
|
},
|
||||||
utils::{get_fallback_fonts, get_font_collection},
|
utils::{get_fallback_fonts, get_font_collection},
|
||||||
};
|
};
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
self as skia,
|
self as skia,
|
||||||
canvas::SaveLayerRec,
|
canvas::SaveLayerRec,
|
||||||
textlayout::{
|
textlayout::{ParagraphBuilder, StyleMetrics, TextDecoration, TextStyle},
|
||||||
LineMetrics, Paragraph, ParagraphBuilder, RectHeightStyle, RectWidthStyle, StyleMetrics,
|
|
||||||
TextDecoration, TextStyle,
|
|
||||||
},
|
|
||||||
Canvas, ImageFilter, Paint, Path,
|
Canvas, ImageFilter, Paint, Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -241,48 +238,24 @@ fn draw_text(
|
|||||||
paragraph_builder_groups: &mut [Vec<ParagraphBuilder>],
|
paragraph_builder_groups: &mut [Vec<ParagraphBuilder>],
|
||||||
) {
|
) {
|
||||||
let text_content = shape.get_text_content();
|
let text_content = shape.get_text_content();
|
||||||
let selrect_width = shape.selrect().width();
|
let layout_info =
|
||||||
let text_width = text_content.get_width(selrect_width);
|
calculate_text_layout_data(shape, text_content, paragraph_builder_groups, true);
|
||||||
let text_height = text_content.get_height(selrect_width);
|
|
||||||
let selrect_height = shape.selrect().height();
|
|
||||||
let mut global_offset_y = match shape.vertical_align() {
|
|
||||||
VerticalAlign::Center => (selrect_height - text_height) / 2.0,
|
|
||||||
VerticalAlign::Bottom => selrect_height - text_height,
|
|
||||||
_ => 0.0,
|
|
||||||
};
|
|
||||||
|
|
||||||
let layer_rec = SaveLayerRec::default();
|
let layer_rec = SaveLayerRec::default();
|
||||||
canvas.save_layer(&layer_rec);
|
canvas.save_layer(&layer_rec);
|
||||||
let mut previous_line_height = text_content.normalized_line_height();
|
|
||||||
|
|
||||||
for paragraph_builder_group in paragraph_builder_groups {
|
for para in &layout_info.paragraphs {
|
||||||
let group_offset_y = global_offset_y;
|
para.paragraph.paint(canvas, (para.x, para.y));
|
||||||
let group_len = paragraph_builder_group.len();
|
for deco in ¶.decorations {
|
||||||
let mut paragraph_offset_y = previous_line_height;
|
draw_text_decorations(
|
||||||
|
canvas,
|
||||||
for (paragraph_index, paragraph_builder) in paragraph_builder_group.iter_mut().enumerate() {
|
&deco.text_style,
|
||||||
let mut paragraph = paragraph_builder.build();
|
Some(deco.y),
|
||||||
paragraph.layout(text_width);
|
deco.thickness,
|
||||||
let xy = (shape.selrect().x(), shape.selrect().y() + group_offset_y);
|
deco.left,
|
||||||
paragraph.paint(canvas, xy);
|
deco.width,
|
||||||
|
);
|
||||||
let line_metrics = paragraph.get_line_metrics();
|
|
||||||
|
|
||||||
if paragraph_index == group_len - 1 {
|
|
||||||
if line_metrics.is_empty() {
|
|
||||||
paragraph_offset_y = paragraph.ideographic_baseline();
|
|
||||||
} else {
|
|
||||||
paragraph_offset_y = paragraph.height();
|
|
||||||
previous_line_height = paragraph.ideographic_baseline();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for line_metrics in paragraph.get_line_metrics().iter() {
|
|
||||||
render_text_decoration(canvas, ¶graph, paragraph_builder, line_metrics, xy);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
global_offset_y += paragraph_offset_y;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -307,7 +280,7 @@ fn draw_text_decorations(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_decoration_metrics(
|
pub fn calculate_decoration_metrics(
|
||||||
style_metrics: &Vec<(usize, &StyleMetrics)>,
|
style_metrics: &Vec<(usize, &StyleMetrics)>,
|
||||||
line_baseline: f32,
|
line_baseline: f32,
|
||||||
) -> (f32, Option<f32>, f32, Option<f32>) {
|
) -> (f32, Option<f32>, f32, Option<f32>) {
|
||||||
@@ -357,106 +330,6 @@ fn calculate_decoration_metrics(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_text_decoration(
|
|
||||||
canvas: &Canvas,
|
|
||||||
skia_paragraph: &Paragraph,
|
|
||||||
builder: &mut ParagraphBuilder,
|
|
||||||
line_metrics: &LineMetrics,
|
|
||||||
xy: (f32, f32),
|
|
||||||
) {
|
|
||||||
let style_metrics: Vec<_> = line_metrics
|
|
||||||
.get_style_metrics(line_metrics.start_index..line_metrics.end_index)
|
|
||||||
.into_iter()
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
let mut current_x_offset = 0.0;
|
|
||||||
let total_chars = line_metrics.end_index - line_metrics.start_index;
|
|
||||||
let line_start_offset = line_metrics.left as f32;
|
|
||||||
|
|
||||||
if total_chars == 0 || style_metrics.is_empty() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
let line_baseline = xy.1 + line_metrics.baseline as f32;
|
|
||||||
let full_text = builder.get_text();
|
|
||||||
|
|
||||||
// Calculate decoration metrics
|
|
||||||
let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) =
|
|
||||||
calculate_decoration_metrics(&style_metrics, line_baseline);
|
|
||||||
|
|
||||||
// Draw decorations per segment (text span)
|
|
||||||
for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() {
|
|
||||||
let text_style = &style_metric.text_style;
|
|
||||||
let style_end = style_metrics
|
|
||||||
.get(i + 1)
|
|
||||||
.map(|(next_i, _)| *next_i)
|
|
||||||
.unwrap_or(line_metrics.end_index);
|
|
||||||
|
|
||||||
let seg_start = (*style_start).max(line_metrics.start_index);
|
|
||||||
let seg_end = style_end.min(line_metrics.end_index);
|
|
||||||
if seg_start >= seg_end {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let start_byte = full_text
|
|
||||||
.char_indices()
|
|
||||||
.nth(seg_start)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.unwrap_or(0);
|
|
||||||
let end_byte = full_text
|
|
||||||
.char_indices()
|
|
||||||
.nth(seg_end)
|
|
||||||
.map(|(i, _)| i)
|
|
||||||
.unwrap_or(full_text.len());
|
|
||||||
let segment_text = &full_text[start_byte..end_byte];
|
|
||||||
|
|
||||||
let rects = skia_paragraph.get_rects_for_range(
|
|
||||||
seg_start..seg_end,
|
|
||||||
RectHeightStyle::Tight,
|
|
||||||
RectWidthStyle::Tight,
|
|
||||||
);
|
|
||||||
let (segment_width, actual_x_offset) = if !rects.is_empty() {
|
|
||||||
let total_width: f32 = rects.iter().map(|r| r.rect.width()).sum();
|
|
||||||
let skia_x_offset = rects
|
|
||||||
.first()
|
|
||||||
.map(|r| r.rect.left - line_start_offset)
|
|
||||||
.unwrap_or(0.0);
|
|
||||||
(total_width, skia_x_offset)
|
|
||||||
} else {
|
|
||||||
let font = skia_paragraph.get_font_at(seg_start);
|
|
||||||
let measured_width = font.measure_text(segment_text, None).0;
|
|
||||||
(measured_width, current_x_offset)
|
|
||||||
};
|
|
||||||
|
|
||||||
let text_left = xy.0 + line_start_offset + actual_x_offset;
|
|
||||||
let text_width = segment_width;
|
|
||||||
|
|
||||||
// Underline
|
|
||||||
if text_style.decoration().ty == TextDecoration::UNDERLINE {
|
|
||||||
draw_text_decorations(
|
|
||||||
canvas,
|
|
||||||
text_style,
|
|
||||||
underline_y,
|
|
||||||
max_underline_thickness,
|
|
||||||
text_left,
|
|
||||||
text_width,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
// Strikethrough
|
|
||||||
if text_style.decoration().ty == TextDecoration::LINE_THROUGH {
|
|
||||||
draw_text_decorations(
|
|
||||||
canvas,
|
|
||||||
text_style,
|
|
||||||
strike_y,
|
|
||||||
max_strike_thickness,
|
|
||||||
text_left,
|
|
||||||
text_width,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
current_x_offset += segment_width;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
fn calculate_total_paragraphs_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 {
|
fn calculate_total_paragraphs_height(paragraphs: &mut [ParagraphBuilder], width: f32) -> f32 {
|
||||||
paragraphs
|
paragraphs
|
||||||
@@ -506,6 +379,29 @@ pub fn render_as_path(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn render_position_data(
|
||||||
|
render_state: &mut RenderState,
|
||||||
|
surface_id: SurfaceId,
|
||||||
|
shape: &Shape,
|
||||||
|
text_content: &TextContent,
|
||||||
|
) {
|
||||||
|
let position_data = calculate_position_data(shape, text_content, false);
|
||||||
|
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
paint.set_color(skia::Color::from_argb(255, 255, 0, 0));
|
||||||
|
paint.set_stroke_width(2.);
|
||||||
|
|
||||||
|
for pd in position_data {
|
||||||
|
let rect = Rect::from_xywh(pd.x, pd.y, pd.width, pd.height);
|
||||||
|
render_state
|
||||||
|
.surfaces
|
||||||
|
.canvas(surface_id)
|
||||||
|
.draw_rect(rect, &paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// How to use it?
|
// How to use it?
|
||||||
// Type::Text(text_content) => {
|
// Type::Text(text_content) => {
|
||||||
// self.surfaces
|
// self.surfaces
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::render::text::calculate_decoration_metrics;
|
||||||
use crate::{
|
use crate::{
|
||||||
math::{Bounds, Matrix, Rect},
|
math::{Bounds, Matrix, Rect},
|
||||||
render::{default_font, DEFAULT_EMOJI_FONT},
|
render::{default_font, DEFAULT_EMOJI_FONT},
|
||||||
@@ -185,6 +186,17 @@ impl TextContentLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TextDecorationSegment {
|
||||||
|
pub kind: skia::textlayout::TextDecoration,
|
||||||
|
pub text_style: skia::textlayout::TextStyle,
|
||||||
|
pub y: f32,
|
||||||
|
pub thickness: f32,
|
||||||
|
pub left: f32,
|
||||||
|
pub width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Check if the current x,y (in paragraph relative coordinates) is inside
|
* Check if the current x,y (in paragraph relative coordinates) is inside
|
||||||
* the paragraph
|
* the paragraph
|
||||||
@@ -204,6 +216,48 @@ fn intersects(paragraph: &skia_safe::textlayout::Paragraph, x: f32, y: f32) -> b
|
|||||||
rects.iter().any(|r| r.rect.contains(&Point::new(x, y)))
|
rects.iter().any(|r| r.rect.contains(&Point::new(x, y)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Performs a text auto layout without width limits.
|
||||||
|
// This should be the same as text_auto_layout.
|
||||||
|
pub fn build_paragraphs_from_paragraph_builders(
|
||||||
|
paragraph_builders: &mut [ParagraphBuilderGroup],
|
||||||
|
width: f32,
|
||||||
|
) -> Vec<Vec<skia::textlayout::Paragraph>> {
|
||||||
|
let paragraphs = paragraph_builders
|
||||||
|
.iter_mut()
|
||||||
|
.map(|builders| {
|
||||||
|
builders
|
||||||
|
.iter_mut()
|
||||||
|
.map(|builder| {
|
||||||
|
let mut paragraph = builder.build();
|
||||||
|
// For auto-width, always layout with infinite width first to get intrinsic width
|
||||||
|
paragraph.layout(width);
|
||||||
|
paragraph
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
paragraphs
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calculate the normalized line height from paragraph builders
|
||||||
|
pub fn calculate_normalized_line_height(
|
||||||
|
paragraph_builders: &mut [ParagraphBuilderGroup],
|
||||||
|
width: f32,
|
||||||
|
) -> f32 {
|
||||||
|
let mut normalized_line_height = 0.0;
|
||||||
|
for paragraph_builder_group in paragraph_builders.iter_mut() {
|
||||||
|
for paragraph_builder in paragraph_builder_group.iter_mut() {
|
||||||
|
let mut paragraph = paragraph_builder.build();
|
||||||
|
paragraph.layout(width);
|
||||||
|
let baseline = paragraph.ideographic_baseline();
|
||||||
|
if baseline > normalized_line_height {
|
||||||
|
normalized_line_height = baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
normalized_line_height
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TextContent {
|
pub struct TextContent {
|
||||||
pub paragraphs: Vec<Paragraph>,
|
pub paragraphs: Vec<Paragraph>,
|
||||||
@@ -440,59 +494,15 @@ impl TextContent {
|
|||||||
paragraph_group
|
paragraph_group
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a text auto layout without width limits.
|
|
||||||
/// This should be the same as text_auto_layout.
|
|
||||||
fn build_paragraphs_from_paragraph_builders(
|
|
||||||
&self,
|
|
||||||
paragraph_builders: &mut [ParagraphBuilderGroup],
|
|
||||||
width: f32,
|
|
||||||
) -> Vec<Vec<skia::textlayout::Paragraph>> {
|
|
||||||
let paragraphs = paragraph_builders
|
|
||||||
.iter_mut()
|
|
||||||
.map(|builders| {
|
|
||||||
builders
|
|
||||||
.iter_mut()
|
|
||||||
.map(|builder| {
|
|
||||||
let mut paragraph = builder.build();
|
|
||||||
// For auto-width, always layout with infinite width first to get intrinsic width
|
|
||||||
paragraph.layout(width);
|
|
||||||
paragraph
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
paragraphs
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Calculate the normalized line height from paragraph builders
|
|
||||||
fn calculate_normalized_line_height(
|
|
||||||
&self,
|
|
||||||
paragraph_builders: &mut [ParagraphBuilderGroup],
|
|
||||||
width: f32,
|
|
||||||
) -> f32 {
|
|
||||||
let mut normalized_line_height = 0.0;
|
|
||||||
for paragraph_builder_group in paragraph_builders.iter_mut() {
|
|
||||||
for paragraph_builder in paragraph_builder_group.iter_mut() {
|
|
||||||
let mut paragraph = paragraph_builder.build();
|
|
||||||
paragraph.layout(width);
|
|
||||||
let baseline = paragraph.ideographic_baseline();
|
|
||||||
if baseline > normalized_line_height {
|
|
||||||
normalized_line_height = baseline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
normalized_line_height
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Performs an Auto Width text layout.
|
/// Performs an Auto Width text layout.
|
||||||
fn text_layout_auto_width(&self) -> TextContentLayoutResult {
|
fn text_layout_auto_width(&self) -> TextContentLayoutResult {
|
||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
|
|
||||||
let normalized_line_height =
|
let normalized_line_height =
|
||||||
self.calculate_normalized_line_height(&mut paragraph_builders, f32::MAX);
|
calculate_normalized_line_height(&mut paragraph_builders, f32::MAX);
|
||||||
|
|
||||||
let paragraphs =
|
let paragraphs =
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::MAX);
|
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::MAX);
|
||||||
|
|
||||||
let (width, height) =
|
let (width, height) =
|
||||||
paragraphs
|
paragraphs
|
||||||
@@ -521,10 +531,9 @@ impl TextContent {
|
|||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
|
|
||||||
let normalized_line_height =
|
let normalized_line_height =
|
||||||
self.calculate_normalized_line_height(&mut paragraph_builders, width);
|
calculate_normalized_line_height(&mut paragraph_builders, width);
|
||||||
|
|
||||||
let paragraphs =
|
let paragraphs = build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
|
||||||
let height = paragraphs
|
let height = paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -546,10 +555,9 @@ impl TextContent {
|
|||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
|
|
||||||
let normalized_line_height =
|
let normalized_line_height =
|
||||||
self.calculate_normalized_line_height(&mut paragraph_builders, width);
|
calculate_normalized_line_height(&mut paragraph_builders, width);
|
||||||
|
|
||||||
let paragraphs =
|
let paragraphs = build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
|
||||||
let paragraph_height = paragraphs
|
let paragraph_height = paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -576,8 +584,7 @@ impl TextContent {
|
|||||||
|
|
||||||
pub fn get_height(&self, width: f32) -> f32 {
|
pub fn get_height(&self, width: f32) -> f32 {
|
||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
let paragraphs =
|
let paragraphs = build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
|
||||||
let paragraph_height = paragraphs
|
let paragraph_height = paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -733,8 +740,7 @@ impl TextContent {
|
|||||||
|
|
||||||
let width = self.width();
|
let width = self.width();
|
||||||
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
let paragraphs =
|
let paragraphs = build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
||||||
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
|
|
||||||
|
|
||||||
paragraphs
|
paragraphs
|
||||||
.iter()
|
.iter()
|
||||||
@@ -863,17 +869,17 @@ impl Paragraph {
|
|||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
pub struct TextSpan {
|
pub struct TextSpan {
|
||||||
text: String,
|
pub text: String,
|
||||||
font_family: FontFamily,
|
pub font_family: FontFamily,
|
||||||
font_size: f32,
|
pub font_size: f32,
|
||||||
line_height: f32,
|
pub line_height: f32,
|
||||||
letter_spacing: f32,
|
pub letter_spacing: f32,
|
||||||
font_weight: i32,
|
pub font_weight: i32,
|
||||||
font_variant_id: Uuid,
|
pub font_variant_id: Uuid,
|
||||||
text_decoration: Option<TextDecoration>,
|
pub text_decoration: Option<TextDecoration>,
|
||||||
text_transform: Option<TextTransform>,
|
pub text_transform: Option<TextTransform>,
|
||||||
text_direction: TextDirection,
|
pub text_direction: TextDirection,
|
||||||
fills: Vec<shapes::Fill>,
|
pub fills: Vec<shapes::Fill>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextSpan {
|
impl TextSpan {
|
||||||
@@ -1045,3 +1051,251 @@ impl TextSpan {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct PositionData {
|
||||||
|
pub paragraph: u32,
|
||||||
|
pub span: u32,
|
||||||
|
pub start_pos: u32,
|
||||||
|
pub end_pos: u32,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub width: f32,
|
||||||
|
pub height: f32,
|
||||||
|
pub direction: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ParagraphLayout {
|
||||||
|
pub paragraph: skia::textlayout::Paragraph,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub spans: Vec<crate::shapes::TextSpan>,
|
||||||
|
pub decorations: Vec<TextDecorationSegment>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextLayoutData {
|
||||||
|
pub position_data: Vec<PositionData>,
|
||||||
|
pub content_rect: Rect,
|
||||||
|
pub paragraphs: Vec<ParagraphLayout>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn direction_to_int(direction: TextDirection) -> u32 {
|
||||||
|
match direction {
|
||||||
|
TextDirection::RTL => 0,
|
||||||
|
TextDirection::LTR => 1,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_text_layout_data(
|
||||||
|
shape: &Shape,
|
||||||
|
text_content: &TextContent,
|
||||||
|
paragraph_builder_groups: &mut [ParagraphBuilderGroup],
|
||||||
|
skip_position_data: bool,
|
||||||
|
) -> TextLayoutData {
|
||||||
|
let selrect_width = shape.selrect().width();
|
||||||
|
let text_width = text_content.get_width(selrect_width);
|
||||||
|
let selrect_height = shape.selrect().height();
|
||||||
|
let x = shape.selrect.x();
|
||||||
|
let base_y = shape.selrect.y();
|
||||||
|
let mut position_data: Vec<PositionData> = Vec::new();
|
||||||
|
let mut previous_line_height = text_content.normalized_line_height();
|
||||||
|
let text_paragraphs = text_content.paragraphs();
|
||||||
|
|
||||||
|
// 1. Calculate paragraph heights
|
||||||
|
let mut paragraph_heights: Vec<f32> = Vec::new();
|
||||||
|
for paragraph_builder_group in paragraph_builder_groups.iter_mut() {
|
||||||
|
let group_len = paragraph_builder_group.len();
|
||||||
|
let mut paragraph_offset_y = previous_line_height;
|
||||||
|
for (builder_index, paragraph_builder) in paragraph_builder_group.iter_mut().enumerate() {
|
||||||
|
let mut skia_paragraph = paragraph_builder.build();
|
||||||
|
skia_paragraph.layout(text_width);
|
||||||
|
if builder_index == group_len - 1 {
|
||||||
|
if skia_paragraph.get_line_metrics().is_empty() {
|
||||||
|
paragraph_offset_y = skia_paragraph.ideographic_baseline();
|
||||||
|
} else {
|
||||||
|
paragraph_offset_y = skia_paragraph.height();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if builder_index == 0 {
|
||||||
|
paragraph_heights.push(skia_paragraph.height());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
previous_line_height = paragraph_offset_y;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Calculate vertical offset and build paragraphs with positions
|
||||||
|
let total_text_height: f32 = paragraph_heights.iter().sum();
|
||||||
|
let vertical_offset = match shape.vertical_align() {
|
||||||
|
VerticalAlign::Center => (selrect_height - total_text_height) / 2.0,
|
||||||
|
VerticalAlign::Bottom => selrect_height - total_text_height,
|
||||||
|
_ => 0.0,
|
||||||
|
};
|
||||||
|
let mut paragraph_layouts: Vec<ParagraphLayout> = Vec::new();
|
||||||
|
let mut y_accum = base_y + vertical_offset;
|
||||||
|
for (i, paragraph_builder_group) in paragraph_builder_groups.iter_mut().enumerate() {
|
||||||
|
// For each paragraph in the group (e.g., fill, stroke, etc.)
|
||||||
|
for paragraph_builder in paragraph_builder_group.iter_mut() {
|
||||||
|
let mut skia_paragraph = paragraph_builder.build();
|
||||||
|
skia_paragraph.layout(text_width);
|
||||||
|
|
||||||
|
let spans = if let Some(text_para) = text_paragraphs.get(i) {
|
||||||
|
text_para.children().to_vec()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Calculate text decorations for this paragraph
|
||||||
|
let mut decorations = Vec::new();
|
||||||
|
let line_metrics = skia_paragraph.get_line_metrics();
|
||||||
|
for line in &line_metrics {
|
||||||
|
let style_metrics: Vec<_> = line
|
||||||
|
.get_style_metrics(line.start_index..line.end_index)
|
||||||
|
.into_iter()
|
||||||
|
.collect();
|
||||||
|
let line_baseline = y_accum + line.baseline as f32;
|
||||||
|
let (max_underline_thickness, underline_y, max_strike_thickness, strike_y) =
|
||||||
|
calculate_decoration_metrics(&style_metrics, line_baseline);
|
||||||
|
for (i, (style_start, style_metric)) in style_metrics.iter().enumerate() {
|
||||||
|
let text_style = &style_metric.text_style;
|
||||||
|
let style_end = style_metrics
|
||||||
|
.get(i + 1)
|
||||||
|
.map(|(next_i, _)| *next_i)
|
||||||
|
.unwrap_or(line.end_index);
|
||||||
|
let seg_start = (*style_start).max(line.start_index);
|
||||||
|
let seg_end = style_end.min(line.end_index);
|
||||||
|
if seg_start >= seg_end {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let rects = skia_paragraph.get_rects_for_range(
|
||||||
|
seg_start..seg_end,
|
||||||
|
skia::textlayout::RectHeightStyle::Tight,
|
||||||
|
skia::textlayout::RectWidthStyle::Tight,
|
||||||
|
);
|
||||||
|
let (segment_width, actual_x_offset) = if !rects.is_empty() {
|
||||||
|
let total_width: f32 = rects.iter().map(|r| r.rect.width()).sum();
|
||||||
|
let skia_x_offset = rects
|
||||||
|
.first()
|
||||||
|
.map(|r| r.rect.left - line.left as f32)
|
||||||
|
.unwrap_or(0.0);
|
||||||
|
(total_width, skia_x_offset)
|
||||||
|
} else {
|
||||||
|
(0.0, 0.0)
|
||||||
|
};
|
||||||
|
let text_left = x + line.left as f32 + actual_x_offset;
|
||||||
|
let text_width = segment_width;
|
||||||
|
use skia::textlayout::TextDecoration;
|
||||||
|
if text_style.decoration().ty == TextDecoration::UNDERLINE {
|
||||||
|
decorations.push(TextDecorationSegment {
|
||||||
|
kind: TextDecoration::UNDERLINE,
|
||||||
|
text_style: (*text_style).clone(),
|
||||||
|
y: underline_y.unwrap_or(line_baseline),
|
||||||
|
thickness: max_underline_thickness,
|
||||||
|
left: text_left,
|
||||||
|
width: text_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if text_style.decoration().ty == TextDecoration::LINE_THROUGH {
|
||||||
|
decorations.push(TextDecorationSegment {
|
||||||
|
kind: TextDecoration::LINE_THROUGH,
|
||||||
|
text_style: (*text_style).clone(),
|
||||||
|
y: strike_y.unwrap_or(line_baseline),
|
||||||
|
thickness: max_strike_thickness,
|
||||||
|
left: text_left,
|
||||||
|
width: text_width,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
paragraph_layouts.push(ParagraphLayout {
|
||||||
|
paragraph: skia_paragraph,
|
||||||
|
x,
|
||||||
|
y: y_accum,
|
||||||
|
spans: spans.clone(),
|
||||||
|
decorations,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
y_accum += paragraph_heights[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate position data from paragraph_layouts
|
||||||
|
if !skip_position_data {
|
||||||
|
for (paragraph_index, para_layout) in paragraph_layouts.iter().enumerate() {
|
||||||
|
let current_y = para_layout.y;
|
||||||
|
let text_paragraph = text_paragraphs.get(paragraph_index);
|
||||||
|
if let Some(text_para) = text_paragraph {
|
||||||
|
let mut span_ranges: Vec<(usize, usize, usize)> = vec![];
|
||||||
|
let mut cur = 0;
|
||||||
|
for (span_index, span) in text_para.children().iter().enumerate() {
|
||||||
|
let text: String = span.apply_text_transform();
|
||||||
|
span_ranges.push((cur, cur + text.len(), span_index));
|
||||||
|
cur += text.len();
|
||||||
|
}
|
||||||
|
for (start, end, span_index) in span_ranges {
|
||||||
|
let rects = para_layout.paragraph.get_rects_for_range(
|
||||||
|
start..end,
|
||||||
|
RectHeightStyle::Tight,
|
||||||
|
RectWidthStyle::Tight,
|
||||||
|
);
|
||||||
|
for textbox in rects {
|
||||||
|
let direction = textbox.direct;
|
||||||
|
let mut rect = textbox.rect;
|
||||||
|
let cy = rect.top + rect.height() / 2.0;
|
||||||
|
let start_pos = para_layout
|
||||||
|
.paragraph
|
||||||
|
.get_glyph_position_at_coordinate((rect.left + 0.1, cy))
|
||||||
|
.position as usize;
|
||||||
|
let end_pos = para_layout
|
||||||
|
.paragraph
|
||||||
|
.get_glyph_position_at_coordinate((rect.right - 0.1, cy))
|
||||||
|
.position as usize;
|
||||||
|
let start_pos = start_pos.saturating_sub(start);
|
||||||
|
let end_pos = end_pos.saturating_sub(start);
|
||||||
|
rect.offset((x, current_y));
|
||||||
|
position_data.push(PositionData {
|
||||||
|
paragraph: paragraph_index as u32,
|
||||||
|
span: span_index as u32,
|
||||||
|
start_pos: start_pos as u32,
|
||||||
|
end_pos: end_pos as u32,
|
||||||
|
x: rect.x(),
|
||||||
|
y: rect.y(),
|
||||||
|
width: rect.width(),
|
||||||
|
height: rect.height(),
|
||||||
|
direction: direction_to_int(direction),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let content_rect = Rect::from_xywh(x, base_y + vertical_offset, text_width, total_text_height);
|
||||||
|
TextLayoutData {
|
||||||
|
position_data,
|
||||||
|
content_rect,
|
||||||
|
paragraphs: paragraph_layouts,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn calculate_position_data(
|
||||||
|
shape: &Shape,
|
||||||
|
text_content: &TextContent,
|
||||||
|
skip_position_data: bool,
|
||||||
|
) -> Vec<PositionData> {
|
||||||
|
let mut text_content = text_content.clone();
|
||||||
|
text_content.update_layout(shape.selrect);
|
||||||
|
|
||||||
|
let mut paragraph_builders = text_content.paragraph_builder_group_from_text(None);
|
||||||
|
let layout_info = calculate_text_layout_data(
|
||||||
|
shape,
|
||||||
|
&text_content,
|
||||||
|
&mut paragraph_builders,
|
||||||
|
skip_position_data,
|
||||||
|
);
|
||||||
|
|
||||||
|
layout_info.position_data
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,13 +2,14 @@ use macros::ToJs;
|
|||||||
|
|
||||||
use super::{fills::RawFillData, fonts::RawFontStyle};
|
use super::{fills::RawFillData, fonts::RawFontStyle};
|
||||||
use crate::math::{Matrix, Point};
|
use crate::math::{Matrix, Point};
|
||||||
use crate::mem;
|
use crate::mem::{self, SerializableResult};
|
||||||
use crate::shapes::{
|
use crate::shapes::{
|
||||||
self, GrowType, Shape, TextAlign, TextDecoration, TextDirection, TextTransform, Type,
|
self, GrowType, Shape, TextAlign, TextDecoration, TextDirection, TextTransform, Type,
|
||||||
};
|
};
|
||||||
use crate::utils::{uuid_from_u32, uuid_from_u32_quartet};
|
use crate::utils::{uuid_from_u32, uuid_from_u32_quartet};
|
||||||
use crate::{
|
use crate::{
|
||||||
with_current_shape_mut, with_state, with_state_mut, with_state_mut_current_shape, STATE,
|
with_current_shape, with_current_shape_mut, with_state, with_state_mut,
|
||||||
|
with_state_mut_current_shape, STATE,
|
||||||
};
|
};
|
||||||
|
|
||||||
const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::<RawTextSpan>();
|
const RAW_SPAN_DATA_SIZE: usize = std::mem::size_of::<RawTextSpan>();
|
||||||
@@ -411,3 +412,37 @@ pub extern "C" fn get_caret_position_at(x: f32, y: f32) -> i32 {
|
|||||||
});
|
});
|
||||||
-1
|
-1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const RAW_POSITION_DATA_SIZE: usize = size_of::<shapes::PositionData>();
|
||||||
|
|
||||||
|
impl SerializableResult for shapes::PositionData {
|
||||||
|
type BytesType = [u8; RAW_POSITION_DATA_SIZE];
|
||||||
|
|
||||||
|
fn from_bytes(bytes: Self::BytesType) -> Self {
|
||||||
|
unsafe { std::mem::transmute(bytes) }
|
||||||
|
}
|
||||||
|
fn as_bytes(&self) -> Self::BytesType {
|
||||||
|
let ptr = self as *const shapes::PositionData as *const u8;
|
||||||
|
let bytes: &[u8] = unsafe { std::slice::from_raw_parts(ptr, RAW_POSITION_DATA_SIZE) };
|
||||||
|
let mut result = [0; RAW_POSITION_DATA_SIZE];
|
||||||
|
result.copy_from_slice(bytes);
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
// The generic trait doesn't know the size of the array. This is why the
|
||||||
|
// clone needs to be here even if it could be generic.
|
||||||
|
fn clone_to_slice(&self, slice: &mut [u8]) {
|
||||||
|
slice.clone_from_slice(&self.as_bytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn calculate_position_data() -> *mut u8 {
|
||||||
|
let mut result = Vec::<shapes::PositionData>::default();
|
||||||
|
with_current_shape!(state, |shape: &Shape| {
|
||||||
|
if let Type::Text(text_content) = &shape.shape_type {
|
||||||
|
result = shapes::calculate_position_data(shape, text_content, false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
mem::write_vec(result)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user