mirror of
https://github.com/penpot/penpot.git
synced 2025-12-11 22:14:05 +01:00
🎉 Add internal TextContent layout data
This commit is contained in:
@@ -602,8 +602,11 @@
|
|||||||
shadows))
|
shadows))
|
||||||
|
|
||||||
(defn set-shape-text-content
|
(defn set-shape-text-content
|
||||||
|
"This function sets shape text content and returns a stream that loads the needed fonts asynchronously"
|
||||||
[shape-id content]
|
[shape-id content]
|
||||||
|
|
||||||
(h/call wasm/internal-module "_clear_shape_text")
|
(h/call wasm/internal-module "_clear_shape_text")
|
||||||
|
|
||||||
(set-shape-vertical-align (get content :vertical-align))
|
(set-shape-vertical-align (get content :vertical-align))
|
||||||
|
|
||||||
(let [paragraph-set (first (get content :children))
|
(let [paragraph-set (first (get content :children))
|
||||||
@@ -635,8 +638,12 @@
|
|||||||
(let [updated-fonts
|
(let [updated-fonts
|
||||||
(-> fonts
|
(-> fonts
|
||||||
(cond-> ^boolean emoji? (f/add-emoji-font))
|
(cond-> ^boolean emoji? (f/add-emoji-font))
|
||||||
(f/add-noto-fonts langs))]
|
(f/add-noto-fonts langs))
|
||||||
(f/store-fonts shape-id updated-fonts))))))
|
result (f/store-fonts shape-id updated-fonts)]
|
||||||
|
|
||||||
|
(h/call wasm/internal-module "_update_shape_text_layout")
|
||||||
|
|
||||||
|
result)))))
|
||||||
|
|
||||||
(defn set-shape-text
|
(defn set-shape-text
|
||||||
[shape-id content]
|
[shape-id content]
|
||||||
|
|||||||
@@ -530,6 +530,7 @@
|
|||||||
|
|
||||||
storeFonts(fonts)
|
storeFonts(fonts)
|
||||||
|
|
||||||
|
console.log("text shape", uuid);
|
||||||
useShape(uuid);
|
useShape(uuid);
|
||||||
Module._set_parent(0, 0, 0, 0);
|
Module._set_parent(0, 0, 0, 0);
|
||||||
Module._set_shape_type(5);
|
Module._set_shape_type(5);
|
||||||
|
|||||||
@@ -471,6 +471,7 @@ export function updateTextShape(root, fonts) {
|
|||||||
|
|
||||||
Module._set_shape_text_content();
|
Module._set_shape_text_content();
|
||||||
}
|
}
|
||||||
|
Module._update_shape_text_layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function addTextShape(text, fonts) {
|
export function addTextShape(text, fonts) {
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ mod performance;
|
|||||||
mod render;
|
mod render;
|
||||||
mod shapes;
|
mod shapes;
|
||||||
mod state;
|
mod state;
|
||||||
mod textlayout;
|
|
||||||
mod tiles;
|
mod tiles;
|
||||||
mod utils;
|
mod utils;
|
||||||
mod uuid;
|
mod uuid;
|
||||||
@@ -372,21 +371,6 @@ pub extern "C" fn set_shape_corners(r1: f32, r2: f32, r3: f32, r4: f32) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
|
||||||
pub extern "C" fn propagate_modifiers(pixel_precision: bool) -> *mut u8 {
|
|
||||||
let bytes = mem::bytes();
|
|
||||||
|
|
||||||
let entries: Vec<_> = bytes
|
|
||||||
.chunks(size_of::<<TransformEntry as SerializableResult>::BytesType>())
|
|
||||||
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
with_state!(state, {
|
|
||||||
let result = shapes::propagate_modifiers(state, &entries, pixel_precision);
|
|
||||||
mem::write_vec(result)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn get_selection_rect() -> *mut u8 {
|
pub extern "C" fn get_selection_rect() -> *mut u8 {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
@@ -483,6 +467,21 @@ pub extern "C" fn clean_modifiers() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn propagate_modifiers(pixel_precision: bool) -> *mut u8 {
|
||||||
|
let bytes = mem::bytes();
|
||||||
|
|
||||||
|
let entries: Vec<_> = bytes
|
||||||
|
.chunks(size_of::<<TransformEntry as SerializableResult>::BytesType>())
|
||||||
|
.map(|data| TransformEntry::from_bytes(data.try_into().unwrap()))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
with_state!(state, {
|
||||||
|
let result = shapes::propagate_modifiers(state, &entries, pixel_precision);
|
||||||
|
mem::write_vec(result)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn set_modifiers() {
|
pub extern "C" fn set_modifiers() {
|
||||||
let bytes = mem::bytes();
|
let bytes = mem::bytes();
|
||||||
|
|||||||
@@ -25,9 +25,6 @@ use crate::shapes::{
|
|||||||
Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, StructureEntry, Type,
|
Blur, BlurType, Corners, Fill, Shadow, Shape, SolidColor, Stroke, StructureEntry, Type,
|
||||||
};
|
};
|
||||||
use crate::state::ShapesPool;
|
use crate::state::ShapesPool;
|
||||||
use crate::textlayout::{
|
|
||||||
paragraph_builder_group_from_text, stroke_paragraph_builder_group_from_text,
|
|
||||||
};
|
|
||||||
use crate::tiles::{self, PendingTiles, TileRect};
|
use crate::tiles::{self, PendingTiles, TileRect};
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
use crate::view::Viewbox;
|
use crate::view::Viewbox;
|
||||||
@@ -622,13 +619,13 @@ impl RenderState {
|
|||||||
let inner_shadows = shape.inner_shadow_paints();
|
let inner_shadows = shape.inner_shadow_paints();
|
||||||
let blur_filter = shape.image_filter(1.);
|
let blur_filter = shape.image_filter(1.);
|
||||||
let count_inner_strokes = shape.count_visible_inner_strokes();
|
let count_inner_strokes = shape.count_visible_inner_strokes();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(&text_content, None);
|
let mut paragraphs = text_content.paragraph_builder_group_from_text(None);
|
||||||
let mut paragraphs_with_shadows =
|
let mut paragraphs_with_shadows =
|
||||||
paragraph_builder_group_from_text(&text_content, Some(true));
|
text_content.paragraph_builder_group_from_text(Some(true));
|
||||||
let mut stroke_paragraphs_list = shape
|
let mut stroke_paragraphs_list = shape
|
||||||
.visible_strokes()
|
.visible_strokes()
|
||||||
.map(|stroke| {
|
.map(|stroke| {
|
||||||
stroke_paragraph_builder_group_from_text(
|
text::stroke_paragraph_builder_group_from_text(
|
||||||
&text_content,
|
&text_content,
|
||||||
stroke,
|
stroke,
|
||||||
&shape.selrect(),
|
&shape.selrect(),
|
||||||
@@ -641,7 +638,7 @@ impl RenderState {
|
|||||||
let mut stroke_paragraphs_with_shadows_list = shape
|
let mut stroke_paragraphs_with_shadows_list = shape
|
||||||
.visible_strokes()
|
.visible_strokes()
|
||||||
.map(|stroke| {
|
.map(|stroke| {
|
||||||
stroke_paragraph_builder_group_from_text(
|
text::stroke_paragraph_builder_group_from_text(
|
||||||
&text_content,
|
&text_content,
|
||||||
stroke,
|
stroke,
|
||||||
&shape.selrect(),
|
&shape.selrect(),
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
use super::{RenderState, SurfaceId};
|
use super::{RenderState, SurfaceId};
|
||||||
use crate::render::strokes;
|
use crate::render::strokes;
|
||||||
use crate::shapes::{Shadow, Shape, Stroke, Type};
|
use crate::shapes::{ParagraphBuilderGroup, Shadow, Shape, Stroke, Type};
|
||||||
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
use skia_safe::{canvas::SaveLayerRec, Paint, Path};
|
||||||
|
|
||||||
use crate::render::text;
|
use crate::render::text;
|
||||||
use crate::textlayout::ParagraphBuilderGroup;
|
|
||||||
|
|
||||||
// Fill Shadows
|
// Fill Shadows
|
||||||
pub fn render_fill_inner_shadows(
|
pub fn render_fill_inner_shadows(
|
||||||
|
|||||||
@@ -1,6 +1,14 @@
|
|||||||
use super::{RenderState, Shape, SurfaceId};
|
use super::{RenderState, Shape, SurfaceId};
|
||||||
use crate::shapes::VerticalAlign;
|
use crate::{
|
||||||
|
math::Rect,
|
||||||
|
shapes::{
|
||||||
|
merge_fills, set_paint_fill, ParagraphBuilderGroup, Stroke, StrokeKind, TextContent,
|
||||||
|
VerticalAlign,
|
||||||
|
},
|
||||||
|
utils::{get_fallback_fonts, get_font_collection},
|
||||||
|
};
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
|
self as skia,
|
||||||
canvas::SaveLayerRec,
|
canvas::SaveLayerRec,
|
||||||
textlayout::{
|
textlayout::{
|
||||||
LineMetrics, Paragraph, ParagraphBuilder, RectHeightStyle, RectWidthStyle, StyleMetrics,
|
LineMetrics, Paragraph, ParagraphBuilder, RectHeightStyle, RectWidthStyle, StyleMetrics,
|
||||||
@@ -9,11 +17,149 @@ use skia_safe::{
|
|||||||
Canvas, ImageFilter, Paint, Path,
|
Canvas, ImageFilter, Paint, Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub fn stroke_paragraph_builder_group_from_text(
|
||||||
|
text_content: &TextContent,
|
||||||
|
stroke: &Stroke,
|
||||||
|
bounds: &Rect,
|
||||||
|
count_inner_strokes: usize,
|
||||||
|
use_shadow: Option<bool>,
|
||||||
|
) -> Vec<ParagraphBuilderGroup> {
|
||||||
|
let fallback_fonts = get_fallback_fonts();
|
||||||
|
let fonts = get_font_collection();
|
||||||
|
let mut paragraph_group = Vec::new();
|
||||||
|
let remove_stroke_alpha = use_shadow.unwrap_or(false) && !stroke.is_transparent();
|
||||||
|
|
||||||
|
for paragraph in text_content.paragraphs() {
|
||||||
|
let mut stroke_paragraphs_map: std::collections::HashMap<usize, ParagraphBuilder> =
|
||||||
|
std::collections::HashMap::new();
|
||||||
|
|
||||||
|
for leaf in paragraph.children().iter() {
|
||||||
|
let text_paint: skia_safe::Handle<_> = merge_fills(leaf.fills(), *bounds);
|
||||||
|
let stroke_paints = get_text_stroke_paints(
|
||||||
|
stroke,
|
||||||
|
bounds,
|
||||||
|
&text_paint,
|
||||||
|
count_inner_strokes,
|
||||||
|
remove_stroke_alpha,
|
||||||
|
);
|
||||||
|
|
||||||
|
let text: String = leaf.apply_text_transform();
|
||||||
|
|
||||||
|
for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() {
|
||||||
|
let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| {
|
||||||
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
|
ParagraphBuilder::new(¶graph_style, fonts)
|
||||||
|
});
|
||||||
|
let stroke_paint = stroke_paint.clone();
|
||||||
|
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
||||||
|
let stroke_style =
|
||||||
|
leaf.to_stroke_style(&stroke_paint, fallback_fonts, remove_alpha);
|
||||||
|
builder.push_style(&stroke_style);
|
||||||
|
builder.add_text(&text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let stroke_paragraphs: Vec<ParagraphBuilder> = (0..stroke_paragraphs_map.len())
|
||||||
|
.map(|i| stroke_paragraphs_map.remove(&i).unwrap())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
paragraph_group.push(stroke_paragraphs);
|
||||||
|
}
|
||||||
|
|
||||||
|
paragraph_group
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_text_stroke_paints(
|
||||||
|
stroke: &Stroke,
|
||||||
|
bounds: &Rect,
|
||||||
|
text_paint: &Paint,
|
||||||
|
count_inner_strokes: usize,
|
||||||
|
remove_stroke_alpha: bool,
|
||||||
|
) -> Vec<Paint> {
|
||||||
|
let mut paints = Vec::new();
|
||||||
|
|
||||||
|
match stroke.kind {
|
||||||
|
StrokeKind::Inner => {
|
||||||
|
let shader = text_paint.shader();
|
||||||
|
let mut is_opaque = true;
|
||||||
|
|
||||||
|
if shader.is_some() {
|
||||||
|
is_opaque = shader.unwrap().is_opaque();
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_opaque && count_inner_strokes == 1 {
|
||||||
|
let mut paint = text_paint.clone();
|
||||||
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
paints.push(paint);
|
||||||
|
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
paint.set_stroke_width(stroke.width * 2.0);
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
|
paints.push(paint);
|
||||||
|
} else {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
if remove_stroke_alpha {
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
|
} else {
|
||||||
|
paint = text_paint.clone();
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
|
paint.set_anti_alias(false);
|
||||||
|
paints.push(paint);
|
||||||
|
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
let image_filter =
|
||||||
|
skia_safe::image_filters::erode((stroke.width, stroke.width), None, None);
|
||||||
|
|
||||||
|
paint.set_image_filter(image_filter);
|
||||||
|
paint.set_anti_alias(false);
|
||||||
|
paint.set_color(skia::Color::BLACK);
|
||||||
|
paint.set_alpha(255);
|
||||||
|
paint.set_blend_mode(skia::BlendMode::DstOut);
|
||||||
|
paints.push(paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
StrokeKind::Center => {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
paint.set_stroke_width(stroke.width);
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
|
paints.push(paint);
|
||||||
|
}
|
||||||
|
StrokeKind::Outer => {
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Stroke);
|
||||||
|
paint.set_blend_mode(skia::BlendMode::DstOver);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
paint.set_stroke_width(stroke.width * 2.0);
|
||||||
|
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
||||||
|
paints.push(paint);
|
||||||
|
|
||||||
|
let mut paint = skia::Paint::default();
|
||||||
|
paint.set_style(skia::PaintStyle::Fill);
|
||||||
|
paint.set_blend_mode(skia::BlendMode::Clear);
|
||||||
|
paint.set_color(skia::Color::TRANSPARENT);
|
||||||
|
paint.set_anti_alias(true);
|
||||||
|
paints.push(paint);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paints
|
||||||
|
}
|
||||||
|
|
||||||
pub fn render(
|
pub fn render(
|
||||||
render_state: Option<&mut RenderState>,
|
render_state: Option<&mut RenderState>,
|
||||||
canvas: Option<&Canvas>,
|
canvas: Option<&Canvas>,
|
||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
paragraph_builders: &mut [Vec<ParagraphBuilder>],
|
||||||
surface_id: Option<SurfaceId>,
|
surface_id: Option<SurfaceId>,
|
||||||
shadow: Option<&Paint>,
|
shadow: Option<&Paint>,
|
||||||
blur: Option<&ImageFilter>,
|
blur: Option<&ImageFilter>,
|
||||||
@@ -36,10 +182,10 @@ pub fn render(
|
|||||||
if let Some(shadow_paint) = shadow {
|
if let Some(shadow_paint) = shadow {
|
||||||
let layer_rec = SaveLayerRec::default().paint(shadow_paint);
|
let layer_rec = SaveLayerRec::default().paint(shadow_paint);
|
||||||
render_canvas.save_layer(&layer_rec);
|
render_canvas.save_layer(&layer_rec);
|
||||||
draw_text(render_canvas, shape, paragraphs);
|
draw_text(render_canvas, shape, paragraph_builders);
|
||||||
render_canvas.restore();
|
render_canvas.restore();
|
||||||
} else {
|
} else {
|
||||||
draw_text(render_canvas, shape, paragraphs);
|
draw_text(render_canvas, shape, paragraph_builders);
|
||||||
}
|
}
|
||||||
|
|
||||||
if blur.is_some() {
|
if blur.is_some() {
|
||||||
@@ -49,7 +195,7 @@ pub fn render(
|
|||||||
render_canvas.restore();
|
render_canvas.restore();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw_text(canvas: &Canvas, shape: &Shape, paragraphs: &mut [Vec<ParagraphBuilder>]) {
|
fn draw_text(canvas: &Canvas, shape: &Shape, paragraph_builders: &mut [Vec<ParagraphBuilder>]) {
|
||||||
// Width
|
// Width
|
||||||
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
|
let paragraph_width = if let crate::shapes::Type::Text(text_content) = &shape.shape_type {
|
||||||
text_content.width()
|
text_content.width()
|
||||||
@@ -59,7 +205,7 @@ fn draw_text(canvas: &Canvas, shape: &Shape, paragraphs: &mut [Vec<ParagraphBuil
|
|||||||
|
|
||||||
// Height
|
// Height
|
||||||
let container_height = shape.selrect().height();
|
let container_height = shape.selrect().height();
|
||||||
let total_content_height = calculate_all_paragraphs_height(paragraphs, paragraph_width);
|
let total_content_height = calculate_all_paragraphs_height(paragraph_builders, paragraph_width);
|
||||||
let mut global_offset_y = match shape.vertical_align() {
|
let mut global_offset_y = match shape.vertical_align() {
|
||||||
VerticalAlign::Center => (container_height - total_content_height) / 2.0,
|
VerticalAlign::Center => (container_height - total_content_height) / 2.0,
|
||||||
VerticalAlign::Bottom => container_height - total_content_height,
|
VerticalAlign::Bottom => container_height - total_content_height,
|
||||||
@@ -68,7 +214,7 @@ fn draw_text(canvas: &Canvas, shape: &Shape, paragraphs: &mut [Vec<ParagraphBuil
|
|||||||
|
|
||||||
let layer_rec = SaveLayerRec::default();
|
let layer_rec = SaveLayerRec::default();
|
||||||
canvas.save_layer(&layer_rec);
|
canvas.save_layer(&layer_rec);
|
||||||
for group in paragraphs {
|
for group in paragraph_builders {
|
||||||
let mut group_offset_y = global_offset_y;
|
let mut group_offset_y = global_offset_y;
|
||||||
let group_len = group.len();
|
let group_len = group.len();
|
||||||
|
|
||||||
|
|||||||
@@ -769,7 +769,7 @@ impl Shape {
|
|||||||
bounds_rect
|
bounds_rect
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Type::Text(ref text_content) = self.shape_type {
|
if let Type::Text(text_content) = &self.shape_type {
|
||||||
let (width, height) = text_content.visual_bounds();
|
let (width, height) = text_content.visual_bounds();
|
||||||
rect.right = rect.left + width;
|
rect.right = rect.left + width;
|
||||||
rect.bottom = rect.top + height;
|
rect.bottom = rect.top + height;
|
||||||
|
|||||||
@@ -14,7 +14,6 @@ use crate::shapes::{
|
|||||||
TransformEntry, Type,
|
TransformEntry, Type,
|
||||||
};
|
};
|
||||||
use crate::state::{ShapesPool, State};
|
use crate::state::{ShapesPool, State};
|
||||||
use crate::textlayout::{auto_height, paragraph_builder_group_from_text};
|
|
||||||
use crate::uuid::Uuid;
|
use crate::uuid::Uuid;
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
#[allow(clippy::too_many_arguments)]
|
||||||
@@ -196,12 +195,15 @@ fn propagate_transform(
|
|||||||
|
|
||||||
let mut transform = entry.transform;
|
let mut transform = entry.transform;
|
||||||
|
|
||||||
if let Type::Text(content) = &shape.shape_type {
|
// NOTA: No puedo utilizar un clone porque entonces estaríamos
|
||||||
match content.grow_type() {
|
// perdiendo la referencia al contenido del layout...
|
||||||
|
if let Type::Text(text_content) = &mut shape.shape_type.clone() {
|
||||||
|
if text_content.needs_update_layout() {
|
||||||
|
text_content.update_layout(shape.selrect);
|
||||||
|
}
|
||||||
|
match text_content.grow_type() {
|
||||||
GrowType::AutoHeight => {
|
GrowType::AutoHeight => {
|
||||||
let paragraph_width = shape_bounds_after.width();
|
let height = text_content.size.height;
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
|
||||||
let height = auto_height(&mut paragraphs, paragraph_width);
|
|
||||||
let resize_transform = math::resize_matrix(
|
let resize_transform = math::resize_matrix(
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
&shape_bounds_after,
|
&shape_bounds_after,
|
||||||
@@ -212,15 +214,10 @@ fn propagate_transform(
|
|||||||
transform.post_concat(&resize_transform);
|
transform.post_concat(&resize_transform);
|
||||||
}
|
}
|
||||||
GrowType::AutoWidth => {
|
GrowType::AutoWidth => {
|
||||||
let paragraph_width = content.width();
|
let width = text_content.width();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
let height = text_content.size.height;
|
||||||
let height = auto_height(&mut paragraphs, paragraph_width);
|
let resize_transform =
|
||||||
let resize_transform = math::resize_matrix(
|
math::resize_matrix(&shape_bounds_after, &shape_bounds_after, width, height);
|
||||||
&shape_bounds_after,
|
|
||||||
&shape_bounds_after,
|
|
||||||
paragraph_width,
|
|
||||||
height,
|
|
||||||
);
|
|
||||||
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
|
shape_bounds_after = shape_bounds_after.transform(&resize_transform);
|
||||||
transform.post_concat(&resize_transform);
|
transform.post_concat(&resize_transform);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,28 +1,138 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
math::{Matrix, Rect},
|
math::{Matrix, Rect},
|
||||||
render::{default_font, DEFAULT_EMOJI_FONT},
|
render::{default_font, DEFAULT_EMOJI_FONT},
|
||||||
textlayout::paragraph_builder_group_from_text,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use core::f32;
|
||||||
|
use macros::ToJs;
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
self as skia,
|
self as skia,
|
||||||
paint::{self, Paint},
|
paint::{self, Paint},
|
||||||
|
textlayout::ParagraphBuilder,
|
||||||
textlayout::ParagraphStyle,
|
textlayout::ParagraphStyle,
|
||||||
};
|
};
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
use super::FontFamily;
|
use super::FontFamily;
|
||||||
use crate::shapes::{self, merge_fills};
|
use crate::shapes::{self, merge_fills};
|
||||||
use crate::textlayout::{auto_height, auto_width};
|
|
||||||
use crate::utils::uuid_from_u32;
|
use crate::utils::uuid_from_u32;
|
||||||
|
use crate::utils::{get_fallback_fonts, get_font_collection};
|
||||||
use crate::wasm::fills::parse_fills_from_bytes;
|
use crate::wasm::fills::parse_fills_from_bytes;
|
||||||
use crate::Uuid;
|
use crate::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
// TODO: maybe move this to the wasm module?
|
||||||
|
pub type ParagraphBuilderGroup = Vec<ParagraphBuilder>;
|
||||||
|
|
||||||
|
#[repr(u8)]
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
|
||||||
pub enum GrowType {
|
pub enum GrowType {
|
||||||
Fixed,
|
Fixed = 0,
|
||||||
AutoWidth,
|
AutoWidth = 1,
|
||||||
AutoHeight,
|
AutoHeight = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, Clone)]
|
||||||
|
pub struct TextContentSize {
|
||||||
|
pub width: f32,
|
||||||
|
pub height: f32,
|
||||||
|
pub max_width: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
const DEFAULT_TEXT_CONTENT_SIZE: f32 = 0.01;
|
||||||
|
|
||||||
|
impl TextContentSize {
|
||||||
|
pub fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
width: DEFAULT_TEXT_CONTENT_SIZE,
|
||||||
|
height: DEFAULT_TEXT_CONTENT_SIZE,
|
||||||
|
max_width: DEFAULT_TEXT_CONTENT_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(width: f32, height: f32, max_width: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
max_width,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_with_size(width: f32, height: f32) -> Self {
|
||||||
|
Self {
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
max_width: DEFAULT_TEXT_CONTENT_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_size(&mut self, width: f32, height: f32) {
|
||||||
|
self.width = width;
|
||||||
|
self.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn copy_finite_size(&mut self, size: TextContentSize) {
|
||||||
|
if f32::is_finite(size.width) {
|
||||||
|
self.width = size.width;
|
||||||
|
}
|
||||||
|
if f32::is_finite(size.max_width) {
|
||||||
|
self.max_width = size.max_width;
|
||||||
|
} else {
|
||||||
|
self.max_width = size.width;
|
||||||
|
}
|
||||||
|
if f32::is_finite(size.height) {
|
||||||
|
self.height = size.height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextContentLayoutResult(
|
||||||
|
Vec<ParagraphBuilderGroup>,
|
||||||
|
Vec<Vec<skia::textlayout::Paragraph>>,
|
||||||
|
TextContentSize,
|
||||||
|
);
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct TextContentLayout {
|
||||||
|
pub paragraph_builders: Vec<ParagraphBuilderGroup>,
|
||||||
|
pub paragraphs: Vec<Vec<skia_safe::textlayout::Paragraph>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Clone for TextContentLayout {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
paragraph_builders: vec![],
|
||||||
|
paragraphs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for TextContentLayout {
|
||||||
|
fn eq(&self, _other: &Self) -> bool {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TextContentLayout {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
paragraph_builders: vec![],
|
||||||
|
paragraphs: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set(
|
||||||
|
&mut self,
|
||||||
|
paragraph_builders: Vec<ParagraphBuilderGroup>,
|
||||||
|
paragraphs: Vec<Vec<skia::textlayout::Paragraph>>,
|
||||||
|
) {
|
||||||
|
self.paragraph_builders = paragraph_builders;
|
||||||
|
self.paragraphs = paragraphs;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_update(&self) -> bool {
|
||||||
|
self.paragraph_builders.is_empty() || self.paragraphs.is_empty()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone)]
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
@@ -30,6 +140,8 @@ pub struct TextContent {
|
|||||||
pub paragraphs: Vec<Paragraph>,
|
pub paragraphs: Vec<Paragraph>,
|
||||||
pub bounds: Rect,
|
pub bounds: Rect,
|
||||||
pub grow_type: GrowType,
|
pub grow_type: GrowType,
|
||||||
|
pub size: TextContentSize,
|
||||||
|
pub layout: TextContentLayout,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TextContent {
|
impl TextContent {
|
||||||
@@ -38,6 +150,8 @@ impl TextContent {
|
|||||||
paragraphs: Vec::new(),
|
paragraphs: Vec::new(),
|
||||||
bounds,
|
bounds,
|
||||||
grow_type,
|
grow_type,
|
||||||
|
size: TextContentSize::default(),
|
||||||
|
layout: TextContentLayout::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -48,6 +162,8 @@ impl TextContent {
|
|||||||
paragraphs,
|
paragraphs,
|
||||||
bounds,
|
bounds,
|
||||||
grow_type,
|
grow_type,
|
||||||
|
size: TextContentSize::new_with_size(bounds.width(), bounds.height()),
|
||||||
|
layout: TextContentLayout::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,9 +195,7 @@ impl TextContent {
|
|||||||
|
|
||||||
pub fn width(&self) -> f32 {
|
pub fn width(&self) -> f32 {
|
||||||
if self.grow_type() == GrowType::AutoWidth {
|
if self.grow_type() == GrowType::AutoWidth {
|
||||||
let temp_paragraphs = paragraph_builder_group_from_text(self, None);
|
self.size.width
|
||||||
let mut temp_paragraphs = temp_paragraphs;
|
|
||||||
auto_width(&mut temp_paragraphs, f32::MAX).ceil()
|
|
||||||
} else {
|
} else {
|
||||||
self.bounds.width()
|
self.bounds.width()
|
||||||
}
|
}
|
||||||
@@ -96,10 +210,7 @@ impl TextContent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn visual_bounds(&self) -> (f32, f32) {
|
pub fn visual_bounds(&self) -> (f32, f32) {
|
||||||
let paragraph_width = self.width();
|
(self.size.width, self.size.height)
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(self, None);
|
|
||||||
let paragraph_height = auto_height(&mut paragraphs, paragraph_width);
|
|
||||||
(paragraph_width, paragraph_height)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn transform(&mut self, transform: &Matrix) {
|
pub fn transform(&mut self, transform: &Matrix) {
|
||||||
@@ -111,6 +222,138 @@ impl TextContent {
|
|||||||
let p2 = transform.map_point(skia::Point::new(right, bottom));
|
let p2 = transform.map_point(skia::Point::new(right, bottom));
|
||||||
self.bounds = Rect::from_ltrb(p1.x, p1.y, p2.x, p2.y);
|
self.bounds = Rect::from_ltrb(p1.x, p1.y, p2.x, p2.y);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Builds the ParagraphBuilders necessary to render
|
||||||
|
/// this text.
|
||||||
|
pub fn paragraph_builder_group_from_text(
|
||||||
|
&self,
|
||||||
|
use_shadow: Option<bool>,
|
||||||
|
) -> Vec<ParagraphBuilderGroup> {
|
||||||
|
let fonts = get_font_collection();
|
||||||
|
let fallback_fonts = get_fallback_fonts();
|
||||||
|
let mut paragraph_group = Vec::new();
|
||||||
|
|
||||||
|
for paragraph in self.paragraphs() {
|
||||||
|
let paragraph_style = paragraph.paragraph_to_style();
|
||||||
|
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
||||||
|
for leaf in paragraph.children() {
|
||||||
|
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
||||||
|
let text_style = leaf.to_style(&self.bounds(), fallback_fonts, remove_alpha);
|
||||||
|
let text = leaf.apply_text_transform();
|
||||||
|
builder.push_style(&text_style);
|
||||||
|
builder.add_text(&text);
|
||||||
|
}
|
||||||
|
paragraph_group.push(vec![builder]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs an Auto Width text layout.
|
||||||
|
fn text_layout_auto_width(&self) -> TextContentLayoutResult {
|
||||||
|
// TODO: Deberíamos comprobar primero que los párrafos
|
||||||
|
// no están generados.
|
||||||
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
|
let paragraphs =
|
||||||
|
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::MAX);
|
||||||
|
let (width, height) =
|
||||||
|
paragraphs
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.fold((0.0, 0.0), |(auto_width, auto_height), paragraph| {
|
||||||
|
(
|
||||||
|
f32::max(paragraph.max_intrinsic_width(), auto_width),
|
||||||
|
auto_height + paragraph.height(),
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
|
let size = TextContentSize::new(width.ceil(), height.ceil(), width.ceil());
|
||||||
|
TextContentLayoutResult(paragraph_builders, paragraphs, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Private function that performs
|
||||||
|
/// Performs an Auto Height text layout.
|
||||||
|
fn text_layout_auto_height(&self) -> TextContentLayoutResult {
|
||||||
|
let width = self.width();
|
||||||
|
// TODO: Deberíamos primero comprobar si existen los
|
||||||
|
// paragraph builders para poder obtener el layout.
|
||||||
|
let mut paragraph_builders = self.paragraph_builder_group_from_text(None);
|
||||||
|
let paragraphs =
|
||||||
|
self.build_paragraphs_from_paragraph_builders(&mut paragraph_builders, f32::INFINITY);
|
||||||
|
let height = paragraphs
|
||||||
|
.iter()
|
||||||
|
.flatten()
|
||||||
|
.fold(0.0, |auto_height, paragraph| {
|
||||||
|
auto_height + paragraph.height()
|
||||||
|
});
|
||||||
|
|
||||||
|
let size = TextContentSize::new_with_size(width.ceil(), height.ceil());
|
||||||
|
TextContentLayoutResult(paragraph_builders, paragraphs, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Performs a Fixed text layout.
|
||||||
|
fn text_layout_fixed(&self) -> TextContentLayoutResult {
|
||||||
|
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);
|
||||||
|
|
||||||
|
let size = TextContentSize::new_with_size(width.ceil(), f32::INFINITY);
|
||||||
|
TextContentLayoutResult(paragraph_builders, paragraphs, size)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn needs_update_layout(&self) -> bool {
|
||||||
|
self.layout.needs_update()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_layout_from_result(&mut self, result: TextContentLayoutResult) {
|
||||||
|
self.layout.set(result.0, result.1);
|
||||||
|
self.size.copy_finite_size(result.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_layout(&mut self, selrect: Rect) -> TextContentSize {
|
||||||
|
self.size.set_size(selrect.width(), selrect.height());
|
||||||
|
// 3. Width and Height Calculation
|
||||||
|
match self.grow_type() {
|
||||||
|
GrowType::AutoHeight => {
|
||||||
|
let result = self.text_layout_auto_height();
|
||||||
|
self.set_layout_from_result(result);
|
||||||
|
}
|
||||||
|
GrowType::AutoWidth => {
|
||||||
|
let result = self.text_layout_auto_width();
|
||||||
|
self.set_layout_from_result(result);
|
||||||
|
}
|
||||||
|
GrowType::Fixed => {
|
||||||
|
let result = self.text_layout_fixed();
|
||||||
|
self.set_layout_from_result(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.size
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for TextContent {
|
impl Default for TextContent {
|
||||||
@@ -119,6 +362,8 @@ impl Default for TextContent {
|
|||||||
paragraphs: vec![],
|
paragraphs: vec![],
|
||||||
bounds: Rect::default(),
|
bounds: Rect::default(),
|
||||||
grow_type: GrowType::Fixed,
|
grow_type: GrowType::Fixed,
|
||||||
|
size: TextContentSize::default(),
|
||||||
|
layout: TextContentLayout::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::{shapes::text::TextContent, textlayout::paragraph_builder_group_from_text};
|
use crate::shapes::text::TextContent;
|
||||||
use skia_safe::{
|
use skia_safe::{
|
||||||
self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob,
|
self as skia, textlayout::Paragraph as SkiaParagraph, FontMetrics, Point, Rect, TextBlob,
|
||||||
};
|
};
|
||||||
@@ -12,17 +12,16 @@ pub struct TextPaths(TextContent);
|
|||||||
// It's an example of how to convert texts to paths
|
// It's an example of how to convert texts to paths
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
impl TextPaths {
|
impl TextPaths {
|
||||||
pub fn new(content: TextContent) -> Self {
|
pub fn new(text_content: TextContent) -> Self {
|
||||||
Self(content)
|
Self(text_content)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
pub fn get_paths(&self, antialias: bool) -> Vec<(skia::Path, skia::Paint)> {
|
||||||
let mut paths = Vec::new();
|
let mut paths = Vec::new();
|
||||||
|
|
||||||
let mut offset_y = self.bounds.y();
|
let mut offset_y = self.bounds.y();
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(&self.0, None);
|
let mut paragraph_builders = self.0.paragraph_builder_group_from_text(None);
|
||||||
|
|
||||||
for paragraphs in paragraphs.iter_mut() {
|
for paragraphs in paragraph_builders.iter_mut() {
|
||||||
for paragraph_builder in paragraphs.iter_mut() {
|
for paragraph_builder in paragraphs.iter_mut() {
|
||||||
// 1. Get paragraph and set the width layout
|
// 1. Get paragraph and set the width layout
|
||||||
let mut skia_paragraph = paragraph_builder.build();
|
let mut skia_paragraph = paragraph_builder.build();
|
||||||
|
|||||||
@@ -1,222 +0,0 @@
|
|||||||
use skia_safe::{self as skia, textlayout::ParagraphBuilder, Paint, Rect};
|
|
||||||
|
|
||||||
use crate::{
|
|
||||||
shapes::{merge_fills, set_paint_fill, Stroke, StrokeKind, TextContent},
|
|
||||||
utils::{get_fallback_fonts, get_font_collection},
|
|
||||||
};
|
|
||||||
|
|
||||||
pub fn auto_width(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
|
||||||
let built_paragraphs = get_built_paragraphs(paragraphs, width);
|
|
||||||
|
|
||||||
built_paragraphs
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.fold(0.0, |auto_width, p| {
|
|
||||||
f32::max(p.max_intrinsic_width(), auto_width)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn auto_height(paragraphs: &mut [Vec<ParagraphBuilder>], width: f32) -> f32 {
|
|
||||||
paragraphs.iter_mut().fold(0.0, |auto_height, p| {
|
|
||||||
p.iter_mut().fold(auto_height, |auto_height, paragraph| {
|
|
||||||
let mut paragraph = paragraph.build();
|
|
||||||
paragraph.layout(width);
|
|
||||||
auto_height + paragraph.height()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn build_paragraphs_with_width(
|
|
||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
|
||||||
width: f32,
|
|
||||||
) -> Vec<Vec<skia_safe::textlayout::Paragraph>> {
|
|
||||||
paragraphs
|
|
||||||
.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(f32::MAX);
|
|
||||||
let intrinsic_width = paragraph.max_intrinsic_width().ceil();
|
|
||||||
// Use the larger of the requested width or intrinsic width to prevent line breaks
|
|
||||||
let final_width = f32::max(width, intrinsic_width);
|
|
||||||
paragraph.layout(final_width);
|
|
||||||
paragraph
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type ParagraphBuilderGroup = Vec<ParagraphBuilder>;
|
|
||||||
|
|
||||||
pub fn paragraph_builder_group_from_text(
|
|
||||||
text_content: &TextContent,
|
|
||||||
use_shadow: Option<bool>,
|
|
||||||
) -> Vec<ParagraphBuilderGroup> {
|
|
||||||
let fonts = get_font_collection();
|
|
||||||
let fallback_fonts = get_fallback_fonts();
|
|
||||||
let mut paragraph_group = Vec::new();
|
|
||||||
|
|
||||||
for paragraph in text_content.paragraphs() {
|
|
||||||
let paragraph_style = paragraph.paragraph_to_style();
|
|
||||||
let mut builder = ParagraphBuilder::new(¶graph_style, fonts);
|
|
||||||
for leaf in paragraph.children() {
|
|
||||||
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
|
||||||
let text_style = leaf.to_style(&text_content.bounds(), fallback_fonts, remove_alpha);
|
|
||||||
let text = leaf.apply_text_transform();
|
|
||||||
builder.push_style(&text_style);
|
|
||||||
builder.add_text(&text);
|
|
||||||
}
|
|
||||||
paragraph_group.push(vec![builder]);
|
|
||||||
}
|
|
||||||
|
|
||||||
paragraph_group
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn stroke_paragraph_builder_group_from_text(
|
|
||||||
text_content: &TextContent,
|
|
||||||
stroke: &Stroke,
|
|
||||||
bounds: &Rect,
|
|
||||||
count_inner_strokes: usize,
|
|
||||||
use_shadow: Option<bool>,
|
|
||||||
) -> Vec<ParagraphBuilderGroup> {
|
|
||||||
let fallback_fonts = get_fallback_fonts();
|
|
||||||
let fonts = get_font_collection();
|
|
||||||
let mut paragraph_group = Vec::new();
|
|
||||||
let remove_stroke_alpha = use_shadow.unwrap_or(false) && !stroke.is_transparent();
|
|
||||||
|
|
||||||
for paragraph in text_content.paragraphs() {
|
|
||||||
let mut stroke_paragraphs_map: std::collections::HashMap<usize, ParagraphBuilder> =
|
|
||||||
std::collections::HashMap::new();
|
|
||||||
|
|
||||||
for leaf in paragraph.children().iter() {
|
|
||||||
let text_paint: skia_safe::Handle<_> = merge_fills(leaf.fills(), *bounds);
|
|
||||||
let stroke_paints = get_text_stroke_paints(
|
|
||||||
stroke,
|
|
||||||
bounds,
|
|
||||||
&text_paint,
|
|
||||||
count_inner_strokes,
|
|
||||||
remove_stroke_alpha,
|
|
||||||
);
|
|
||||||
|
|
||||||
let text: String = leaf.apply_text_transform();
|
|
||||||
|
|
||||||
for (paint_idx, stroke_paint) in stroke_paints.iter().enumerate() {
|
|
||||||
let builder = stroke_paragraphs_map.entry(paint_idx).or_insert_with(|| {
|
|
||||||
let paragraph_style = paragraph.paragraph_to_style();
|
|
||||||
ParagraphBuilder::new(¶graph_style, fonts)
|
|
||||||
});
|
|
||||||
let stroke_paint = stroke_paint.clone();
|
|
||||||
let remove_alpha = use_shadow.unwrap_or(false) && !leaf.is_transparent();
|
|
||||||
let stroke_style =
|
|
||||||
leaf.to_stroke_style(&stroke_paint, fallback_fonts, remove_alpha);
|
|
||||||
builder.push_style(&stroke_style);
|
|
||||||
builder.add_text(&text);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let stroke_paragraphs: Vec<ParagraphBuilder> = (0..stroke_paragraphs_map.len())
|
|
||||||
.map(|i| stroke_paragraphs_map.remove(&i).unwrap())
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
paragraph_group.push(stroke_paragraphs);
|
|
||||||
}
|
|
||||||
|
|
||||||
paragraph_group
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_built_paragraphs(
|
|
||||||
paragraphs: &mut [Vec<ParagraphBuilder>],
|
|
||||||
width: f32,
|
|
||||||
) -> Vec<Vec<skia_safe::textlayout::Paragraph>> {
|
|
||||||
build_paragraphs_with_width(paragraphs, width)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_text_stroke_paints(
|
|
||||||
stroke: &Stroke,
|
|
||||||
bounds: &Rect,
|
|
||||||
text_paint: &Paint,
|
|
||||||
count_inner_strokes: usize,
|
|
||||||
remove_stroke_alpha: bool,
|
|
||||||
) -> Vec<Paint> {
|
|
||||||
let mut paints = Vec::new();
|
|
||||||
|
|
||||||
match stroke.kind {
|
|
||||||
StrokeKind::Inner => {
|
|
||||||
let shader = text_paint.shader();
|
|
||||||
let mut is_opaque = true;
|
|
||||||
|
|
||||||
if shader.is_some() {
|
|
||||||
is_opaque = shader.unwrap().is_opaque();
|
|
||||||
}
|
|
||||||
|
|
||||||
if is_opaque && count_inner_strokes == 1 {
|
|
||||||
let mut paint = text_paint.clone();
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paints.push(paint);
|
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::SrcIn);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paint.set_stroke_width(stroke.width * 2.0);
|
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
|
||||||
paints.push(paint);
|
|
||||||
} else {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
if remove_stroke_alpha {
|
|
||||||
paint.set_color(skia::Color::BLACK);
|
|
||||||
paint.set_alpha(255);
|
|
||||||
} else {
|
|
||||||
paint = text_paint.clone();
|
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
paint.set_anti_alias(false);
|
|
||||||
paints.push(paint);
|
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
let image_filter =
|
|
||||||
skia_safe::image_filters::erode((stroke.width, stroke.width), None, None);
|
|
||||||
|
|
||||||
paint.set_image_filter(image_filter);
|
|
||||||
paint.set_anti_alias(false);
|
|
||||||
paint.set_color(skia::Color::BLACK);
|
|
||||||
paint.set_alpha(255);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::DstOut);
|
|
||||||
paints.push(paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
StrokeKind::Center => {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paint.set_stroke_width(stroke.width);
|
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
|
||||||
paints.push(paint);
|
|
||||||
}
|
|
||||||
StrokeKind::Outer => {
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Stroke);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::DstOver);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paint.set_stroke_width(stroke.width * 2.0);
|
|
||||||
set_paint_fill(&mut paint, &stroke.fill, bounds, remove_stroke_alpha);
|
|
||||||
paints.push(paint);
|
|
||||||
|
|
||||||
let mut paint = skia::Paint::default();
|
|
||||||
paint.set_style(skia::PaintStyle::Fill);
|
|
||||||
paint.set_blend_mode(skia::BlendMode::Clear);
|
|
||||||
paint.set_color(skia::Color::TRANSPARENT);
|
|
||||||
paint.set_anti_alias(true);
|
|
||||||
paints.push(paint);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
paints
|
|
||||||
}
|
|
||||||
@@ -2,10 +2,7 @@ use macros::ToJs;
|
|||||||
|
|
||||||
use crate::mem;
|
use crate::mem;
|
||||||
use crate::shapes::{GrowType, RawTextData, Type};
|
use crate::shapes::{GrowType, RawTextData, Type};
|
||||||
use crate::textlayout::{
|
use crate::{with_current_shape_mut, STATE};
|
||||||
auto_height, build_paragraphs_with_width, paragraph_builder_group_from_text,
|
|
||||||
};
|
|
||||||
use crate::{with_current_shape, with_current_shape_mut, STATE};
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
|
#[derive(Debug, PartialEq, Clone, Copy, ToJs)]
|
||||||
#[repr(u8)]
|
#[repr(u8)]
|
||||||
@@ -57,52 +54,38 @@ pub extern "C" fn set_shape_grow_type(grow_type: u8) {
|
|||||||
|
|
||||||
with_current_shape_mut!(state, |shape: &mut Shape| {
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
if let Type::Text(text_content) = &mut shape.shape_type {
|
if let Type::Text(text_content) = &mut shape.shape_type {
|
||||||
text_content.set_grow_type(grow_type.into());
|
text_content.set_grow_type(GrowType::from(grow_type));
|
||||||
|
} else {
|
||||||
|
panic!("Trying to update grow type in a shape that it's not a text shape");
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[no_mangle]
|
#[no_mangle]
|
||||||
pub extern "C" fn get_text_dimensions() -> *mut u8 {
|
pub extern "C" fn get_text_dimensions() -> *mut u8 {
|
||||||
let mut width = 0.01;
|
let mut ptr = std::ptr::null_mut();
|
||||||
let mut height = 0.01;
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
let mut m_width = 0.01;
|
if let Type::Text(content) = &mut shape.shape_type {
|
||||||
|
let text_content_size = content.update_layout(shape.selrect);
|
||||||
|
|
||||||
with_current_shape!(state, |shape: &Shape| {
|
let mut bytes = vec![0; 12];
|
||||||
width = shape.selrect.width();
|
bytes[0..4].clone_from_slice(&text_content_size.width.to_le_bytes());
|
||||||
height = shape.selrect.height();
|
bytes[4..8].clone_from_slice(&text_content_size.height.to_le_bytes());
|
||||||
|
bytes[8..12].clone_from_slice(&text_content_size.max_width.to_le_bytes());
|
||||||
if let Type::Text(content) = &shape.shape_type {
|
ptr = mem::write_bytes(bytes)
|
||||||
// 1. Reset Paragraphs
|
|
||||||
let paragraph_width = content.width();
|
|
||||||
let mut paragraphs = paragraph_builder_group_from_text(content, None);
|
|
||||||
let built_paragraphs = build_paragraphs_with_width(&mut paragraphs, paragraph_width);
|
|
||||||
|
|
||||||
// 2. Max Width Calculation
|
|
||||||
m_width = built_paragraphs
|
|
||||||
.iter()
|
|
||||||
.flatten()
|
|
||||||
.fold(0.0, |max_width, p| f32::max(p.max_width(), max_width));
|
|
||||||
|
|
||||||
// 3. Width and Height Calculation
|
|
||||||
match content.grow_type() {
|
|
||||||
GrowType::AutoHeight => {
|
|
||||||
let mut paragraph_height = paragraph_builder_group_from_text(content, None);
|
|
||||||
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
|
||||||
}
|
|
||||||
GrowType::AutoWidth => {
|
|
||||||
width = paragraph_width;
|
|
||||||
let mut paragraph_height = paragraph_builder_group_from_text(content, None);
|
|
||||||
height = auto_height(&mut paragraph_height, paragraph_width).ceil();
|
|
||||||
}
|
|
||||||
GrowType::Fixed => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut bytes = vec![0; 12];
|
// FIXME: I think it should be better if instead of returning
|
||||||
bytes[0..4].clone_from_slice(&width.to_le_bytes());
|
// a NULL ptr we failed gracefully.
|
||||||
bytes[4..8].clone_from_slice(&height.to_le_bytes());
|
ptr
|
||||||
bytes[8..12].clone_from_slice(&m_width.to_le_bytes());
|
}
|
||||||
mem::write_bytes(bytes)
|
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern "C" fn update_shape_text_layout() {
|
||||||
|
with_current_shape_mut!(state, |shape: &mut Shape| {
|
||||||
|
if let Type::Text(text_content) = &mut shape.shape_type {
|
||||||
|
text_content.update_layout(shape.selrect);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user