Compare commits

...

1 Commits

Author SHA1 Message Date
alonso.torres
baa90ea8bf WIP 2025-12-10 09:45:06 +01:00
11 changed files with 374 additions and 60 deletions

View File

@@ -379,6 +379,23 @@
(->> (rx/from added)
(rx/map process-wasm-object)))))))
(when render-wasm?
(->> stream
(rx/filter (ptk/type? :wasm/position-data))
(rx/map deref)
(rx/filter
(fn [{:keys [position-data]}]
(some? position-data)))
(rx/map
(fn [{:keys [id position-data]}]
(prn "???" id position-data)
(dwsh/update-shapes
[id]
(fn [shape]
(.log js/console (clj->js shape))
(assoc shape :position-data position-data))
{:ignore-wasm? true})))))
(->> stream
(rx/filter dch/commit?)
(rx/map deref)

View File

@@ -50,7 +50,8 @@
([ids update-fn] (update-shapes ids update-fn nil))
([ids update-fn
{:keys [reg-objects? save-undo? stack-undo? attrs ignore-tree page-id
ignore-touched undo-group with-objects? changed-sub-attr]
ignore-touched undo-group with-objects? changed-sub-attr
ignore-wasm?]
:or {reg-objects? false
save-undo? true
stack-undo? false
@@ -89,6 +90,7 @@
:ignore-tree ignore-tree
:ignore-touched ignore-touched
:with-objects? with-objects?})
(assoc :ignore-wasm? ignore-wasm?)
(cond-> undo-group
(pcb/set-undo-group undo-group)))

View File

@@ -6,6 +6,7 @@
(ns app.main.ui.workspace.viewport.debug
(:require
[app.render-wasm.api :as wasm.api]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.helpers :as cfh]
@@ -275,3 +276,29 @@
:y2 (:y end-p)
:style {:stroke "red"
:stroke-width (/ 1 zoom)}}]))]))))
(mf/defc debug-text-position-data
{::mf/wrap-props false}
[props]
(let [objects (unchecked-get props "objects")
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
:width width
:height height
:fill "none"
:strokeWidth 1
:stroke "red"}]
)))

View File

@@ -635,6 +635,10 @@
:hover-top-frame-id @hover-top-frame-id
:zoom zoom}])
[:& wvd/debug-text-position-data {:selected-shapes selected-shapes
:objects base-objects
:zoom zoom}]
(when show-selection-handlers?
[:g.selection-handlers {:clipPath "url(#clip-handlers)"}
(when-not text-editing?

View File

@@ -7,6 +7,9 @@
(ns app.render-wasm.api
"A WASM based render API"
(:require
[potok.v2.core :as ptk]
[app.main.data.helpers :as dsh]
[app.main.ui.shapes.text]
["react-dom/server" :as rds]
[app.common.data :as d]
[app.common.data.macros :as dm]
@@ -127,10 +130,18 @@
(render ts)))))
(declare get-text-dimensions)
(declare calculate-position-data)
(defn update-text-rect!
[id]
(when wasm/context-initialized?
(let [objects (dsh/lookup-page-objects @st/state)
shape (get objects id)
position-data (calculate-position-data shape)]
(.log js/console (:name shape) (clj->js position-data))
(st/emit!
(ptk/data-event :wasm/position-data {:id id :position-data position-data})))
(mw/emit!
{:cmd :index/update-text-rect
:page-id (:current-page-id @st/state)
@@ -988,10 +999,7 @@
(run!
(fn [id]
(f/update-text-layout id)
(mw/emit! {:cmd :index/update-text-rect
:page-id (:current-page-id @st/state)
:shape-id id
:dimensions (get-text-dimensions id)})))))
(update-text-rect! id)))))
(defn process-pending
([shapes thumbnails full on-complete]
@@ -1347,6 +1355,58 @@
(h/call wasm/internal-module "_end_temp_objects")
content)))
(def POSITION-DATA-U8-SIZE 36)
(def POSITION-DATA-U32-SIZE (/ POSITION-DATA-U8-SIZE 4))
(defn calculate-position-data
[shape]
(use-shape (:id shape))
(let [heapf32 (mem/get-heap-f32)
heapu32 (mem/get-heap-u32)
offset (-> (h/call wasm/internal-module "_calc_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
:width width
:height height
: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
[module]
(let [default-fn (unchecked-get module "default")

View File

@@ -45,4 +45,23 @@
:center (gpt/point cx cy)
: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}))

View File

@@ -6,6 +6,7 @@
(ns debug
(:require
[app.render-wasm.api :as wasm.api]
[app.common.data :as d]
[app.common.data.macros :as dm]
[app.common.files.repair :as cfr]
@@ -456,3 +457,10 @@
(defn ^:export network-averages
[]
(.log js/console (clj->js @http/network-averages)))
(defn ^:export tmp
[]
(let [objects (dsh/lookup-page-objects @st/state)
shape (->> (get-selected @st/state) (first) (get objects))]
(wasm.api/calculate-position-data shape))
)

View File

@@ -847,6 +847,8 @@ impl RenderState {
);
}
}
// text::render_position_data(self, fills_surface_id, &shape, &text_content);
}
}
_ => {

View File

@@ -4,6 +4,7 @@ use crate::{
shapes::{
merge_fills, set_paint_fill, ParagraphBuilderGroup, Stroke, StrokeKind, TextContent,
VerticalAlign,
calc_position_data
},
utils::{get_fallback_fonts, get_font_collection},
};
@@ -504,6 +505,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 = calc_position_data(shape, text_content);
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?
// Type::Text(text_content) => {
// self.surfaces

View File

@@ -204,6 +204,49 @@ fn intersects(paragraph: &skia_safe::textlayout::Paragraph, x: f32, y: f32) -> b
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)]
pub struct TextContent {
pub paragraphs: Vec<Paragraph>,
@@ -440,59 +483,15 @@ impl TextContent {
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.
fn text_layout_auto_width(&self) -> TextContentLayoutResult {
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
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 =
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::MAX);
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::MAX);
let (width, height) =
paragraphs
@@ -521,10 +520,10 @@ impl TextContent {
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
let normalized_line_height =
self.calculate_normalized_line_height(&mut paragraph_builders, width);
calculate_normalized_line_height(&mut paragraph_builders, width);
let paragraphs =
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
let height = paragraphs
.iter()
.flatten()
@@ -546,10 +545,10 @@ impl TextContent {
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
let normalized_line_height =
self.calculate_normalized_line_height(&mut paragraph_builders, width);
calculate_normalized_line_height(&mut paragraph_builders, width);
let paragraphs =
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
let paragraph_height = paragraphs
.iter()
.flatten()
@@ -577,7 +576,7 @@ impl TextContent {
pub fn get_height(&self, width: f32) -> f32 {
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
let paragraphs =
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
let paragraph_height = paragraphs
.iter()
.flatten()
@@ -734,7 +733,7 @@ impl TextContent {
let width = self.width();
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
let paragraphs =
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
build_paragraphs_from_paragraph_builders(&mut paragraph_builders, width);
paragraphs
.iter()
@@ -1045,3 +1044,121 @@ impl TextSpan {
})
}
}
#[allow(dead_code)]
#[derive(Debug)]
pub struct PositionData {
pub paragraph: u32, // 4
pub span: u32, // 4
pub start_pos: u32, // 4
pub end_pos: u32, // 4
pub x: f32, // 4
pub y: f32, // 4
pub width: f32, // 4
pub height: f32, // 4
pub direction: u32 // 4, u32 to align with 32 bytes
}
fn direction_to_int(direction: TextDirection) -> u32 {
match direction {
TextDirection::RTL => 0,
TextDirection::LTR => 1
}
}
//fn get_unicode_substring(full_text: &str, start: usize, end: usize) -> String {
// let chars: Vec<char> = full_text.chars().collect();
// chars[start..end].iter().collect()
//}
pub fn calc_position_data(
shape: &Shape,
text_content: &TextContent
) -> Vec<PositionData> {
let mut result: Vec<PositionData> = Vec::default();
let mut text_content = text_content.clone();
text_content.update_layout(shape.selrect);
let rect = text_content.content_rect(&shape.selrect, shape.vertical_align);
let x = rect.x();
let mut y = rect.y();
let fonts = get_font_collection();
let fallback_fonts = get_fallback_fonts();
for (paragraph_index, paragraph) in text_content.paragraphs().iter().enumerate() {
let mut paragraph_text = String::default();
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
let mut span_ranges: Vec<(usize, usize, usize)> = vec![];
let mut cur = 0;
for (span_index, span) in paragraph.children().iter().enumerate() {
let text_style = span.to_style(
&text_content.bounds(),
fallback_fonts,
false,
paragraph.line_height(),
);
let text: String = span.apply_text_transform();
builder.push_style(&text_style);
builder.add_text(&text);
span_ranges.push((cur, cur + text.len(), span_index));
cur += text.len();
paragraph_text += &text;
}
let mut p = builder.build();
p.layout(shape.selrect.width());
for (start, end, span_index) in span_ranges {
let rects = p.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 = p
.get_glyph_position_at_coordinate((rect.left + 0.1, cy))
.position as usize;
let end_pos = p
.get_glyph_position_at_coordinate((rect.right - 0.1, cy))
.position as usize;
// start_pos and end_pos are relative to the paragraph but we
// want it relative to the span
let start_pos = start_pos - start;
let end_pos = end_pos - start;
rect.offset((x, y));
result.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)
});
}
}
y += p.height();
}
return result;
}

View File

@@ -2,13 +2,13 @@ use macros::ToJs;
use super::{fills::RawFillData, fonts::RawFontStyle};
use crate::math::{Matrix, Point};
use crate::mem;
use crate::mem::{self, SerializableResult};
use crate::shapes::{
self, GrowType, Shape, TextAlign, TextDecoration, TextDirection, TextTransform, Type,
};
use crate::utils::{uuid_from_u32, uuid_from_u32_quartet};
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>();
@@ -411,3 +411,37 @@ pub extern "C" fn get_caret_position_at(x: f32, y: f32) -> i32 {
});
-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 calc_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::calc_position_data(shape, &text_content);
}
});
mem::write_vec(result)
}