🎉 Add text fills

This commit is contained in:
Elena Torro 2025-05-09 15:53:11 +02:00
parent c2ce7c6cf6
commit 42ef2f929a
11 changed files with 451 additions and 279 deletions

View file

@ -10,7 +10,7 @@ mod strokes;
mod surfaces;
mod text;
use skia_safe::{self as skia, image, Matrix, RRect, Rect};
use skia_safe::{self as skia, Matrix, RRect, Rect};
use std::borrow::Cow;
use std::collections::{HashMap, HashSet};
@ -19,7 +19,7 @@ use options::RenderOptions;
use surfaces::{SurfaceId, Surfaces};
use crate::performance;
use crate::shapes::{modified_children_ids, Corners, Fill, Shape, StructureEntry, Type};
use crate::shapes::{modified_children_ids, Corners, Shape, StructureEntry, Type};
use crate::tiles::{self, TileRect, TileViewbox, TileWithDistance};
use crate::uuid::Uuid;
use crate::view::Viewbox;
@ -427,13 +427,11 @@ impl RenderState {
text::render(self, &shape, &paragraphs, None, None);
for stroke in shape.strokes().rev() {
let mut image: Option<image::Image> = None;
if let Fill::Image(image_fill) = &stroke.fill {
image = self.images.get(&image_fill.id()).cloned();
}
let stroke_paints = shape.get_text_stroke_paint(stroke, image.as_ref());
let stroke_paragraphs = text_content
.get_skia_stroke_paragraphs(self.fonts.font_collection(), &stroke_paints);
let stroke_paragraphs = text_content.get_skia_stroke_paragraphs(
stroke,
&shape.selrect(),
self.fonts.font_collection(),
);
shadows::render_text_drop_shadows(self, &shape, &stroke_paragraphs, antialias);
text::render(
self,

View file

@ -1,5 +1,4 @@
use skia_safe::Image;
use skia_safe::{self as skia, paint::Paint};
use skia_safe::{self as skia};
use crate::render::BlendMode;
use crate::uuid::Uuid;
@ -806,87 +805,6 @@ impl Shape {
pub fn has_fills(&self) -> bool {
!self.fills.is_empty()
}
fn set_paint_fill(&self, paint: &mut Paint, fill: &Fill, image: Option<Image>) {
match fill {
Fill::Solid(SolidColor(color)) => {
paint.set_color(*color);
}
Fill::LinearGradient(gradient) => {
paint.set_shader(gradient.to_linear_shader(&self.selrect()));
}
Fill::RadialGradient(gradient) => {
paint.set_shader(gradient.to_radial_shader(&self.selrect()));
}
Fill::Image(image_fill) => {
if let Some(image) = image {
let position = (self.selrect().x(), self.selrect().y());
let sampling_options = skia::SamplingOptions::new(
skia::FilterMode::Linear,
skia::MipmapMode::Nearest,
);
let tile_modes = (skia::TileMode::Clamp, skia::TileMode::Clamp);
let mut matrix = skia::Matrix::default();
matrix.set_translate(position);
let shader = image.to_shader(tile_modes, sampling_options, &matrix);
paint.set_shader(shader);
paint.set_alpha(image_fill.opacity());
}
}
}
}
pub fn get_text_stroke_paint(&self, stroke: &Stroke, image: Option<&Image>) -> Vec<Paint> {
let mut paints = Vec::new();
match stroke.kind {
StrokeKind::Inner => {
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::DstOver);
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::SrcATop);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
self.set_paint_fill(&mut paint, &stroke.fill, image.cloned());
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);
self.set_paint_fill(&mut paint, &stroke.fill, image.cloned());
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);
self.set_paint_fill(&mut paint, &stroke.fill, image.cloned());
paints.push(paint);
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::Clear);
paint.set_anti_alias(true);
paints.push(paint);
}
}
paints
}
}
/*

View file

@ -1,6 +1,7 @@
use skia_safe::{self as skia, Rect};
use skia_safe::{self as skia, Paint, Rect};
pub use super::Color;
use crate::utils::get_image;
use crate::uuid::Uuid;
#[derive(Debug, Clone, PartialEq)]
@ -176,3 +177,79 @@ impl Fill {
}
}
}
pub fn get_fill_shader(fill: &Fill, bounding_box: &Rect) -> Option<skia::Shader> {
match fill {
Fill::Solid(SolidColor(color)) => Some(skia::shaders::color(*color)),
Fill::LinearGradient(gradient) => gradient.to_linear_shader(bounding_box),
Fill::RadialGradient(gradient) => gradient.to_radial_shader(bounding_box),
Fill::Image(image_fill) => {
let mut image_shader = None;
let image = get_image(&image_fill.id);
if let Some(image) = image {
let sampling_options =
skia::SamplingOptions::new(skia::FilterMode::Linear, skia::MipmapMode::Nearest);
// FIXME no image ratio applied, centered to the current rect
let tile_modes = (skia::TileMode::Clamp, skia::TileMode::Clamp);
let image_width = image_fill.width as f32;
let image_height = image_fill.height as f32;
let scale_x = bounding_box.width() / image_width;
let scale_y = bounding_box.height() / image_height;
let scale = scale_x.max(scale_y);
let scaled_width = image_width * scale;
let scaled_height = image_height * scale;
let pos_x = bounding_box.left() - (scaled_width - bounding_box.width()) / 2.0;
let pos_y = bounding_box.top() - (scaled_height - bounding_box.height()) / 2.0;
let mut matrix = skia::Matrix::new_identity();
matrix.pre_translate((pos_x, pos_y));
matrix.pre_scale((scale, scale), None);
let opacity = image_fill.opacity();
let alpha_color = skia::Color4f::new(1.0, 1.0, 1.0, opacity as f32 / 255.0);
let alpha_shader = skia::shaders::color(alpha_color.to_color());
image_shader = image.to_shader(tile_modes, sampling_options, &matrix);
if let Some(shader) = image_shader {
image_shader = Some(skia::shaders::blend(
skia::Blender::mode(skia::BlendMode::DstIn),
shader,
alpha_shader,
));
}
}
image_shader
}
}
}
pub fn merge_fills(fills: &[Fill], bounding_box: Rect) -> skia::Paint {
let mut combined_shader: Option<skia::Shader> = None;
let mut fills_paint = skia::Paint::default();
for fill in fills {
let shader = get_fill_shader(fill, &bounding_box);
if let Some(shader) = shader {
combined_shader = match combined_shader {
Some(existing_shader) => Some(skia::shaders::blend(
skia::Blender::mode(skia::BlendMode::Overlay),
existing_shader,
shader,
)),
None => Some(shader),
};
}
}
fills_paint.set_shader(combined_shader.clone());
fills_paint
}
pub fn set_paint_fill(paint: &mut Paint, fill: &Fill, bounding_box: &Rect) {
let shader = get_fill_shader(fill, bounding_box);
if let Some(shader) = shader {
paint.set_shader(shader);
}
}

View file

@ -9,7 +9,9 @@ use skia_safe::{
};
use super::FontFamily;
use crate::shapes::{self, merge_fills, set_paint_fill, Stroke, StrokeKind};
use crate::utils::uuid_from_u32;
use crate::wasm::fills::parse_fills_from_bytes;
use crate::Uuid;
#[derive(Debug, PartialEq, Clone, Copy)]
@ -52,9 +54,9 @@ pub fn set_paragraphs_width(width: f32, paragraphs: &mut Vec<Vec<skia::textlayou
impl TextContent {
pub fn new(bounds: Rect, grow_type: GrowType) -> Self {
Self {
paragraphs: Vec::new(),
bounds,
grow_type,
..Self::default()
}
}
@ -100,7 +102,7 @@ impl TextContent {
let paragraph_style = p.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &p.children {
let text_style = leaf.to_style(p);
let text_style = leaf.to_style(p, &self.bounds); // FIXME
let text = leaf.apply_text_transform(p.text_transform);
builder.push_style(&text_style);
builder.add_text(&text);
@ -115,10 +117,12 @@ impl TextContent {
pub fn to_stroke_paragraphs(
&self,
stroke: &Stroke,
bounds: &Rect,
fonts: &FontCollection,
stroke_paints: &Vec<Paint>,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
let mut paragraph_group = Vec::new();
let stroke_paints = get_text_stroke_paints(stroke, bounds);
for stroke_paint in stroke_paints {
let mut stroke_paragraphs = Vec::new();
@ -126,7 +130,7 @@ impl TextContent {
let paragraph_style = paragraph.paragraph_to_style();
let mut builder = ParagraphBuilder::new(&paragraph_style, fonts);
for leaf in &paragraph.children {
let stroke_style = leaf.to_stroke_style(paragraph, stroke_paint);
let stroke_style = leaf.to_stroke_style(paragraph, &stroke_paint);
let text: String = leaf.apply_text_transform(paragraph.text_transform);
builder.push_style(&stroke_style);
builder.add_text(&text);
@ -163,10 +167,11 @@ impl TextContent {
pub fn get_skia_stroke_paragraphs(
&self,
stroke: &Stroke,
bounds: &Rect,
fonts: &FontCollection,
paints: &Vec<Paint>,
) -> Vec<Vec<skia::textlayout::Paragraph>> {
self.collect_paragraphs(self.to_stroke_paragraphs(fonts, paints))
self.collect_paragraphs(self.to_stroke_paragraphs(stroke, bounds, fonts))
}
pub fn grow_type(&self) -> GrowType {
@ -190,6 +195,7 @@ impl Default for TextContent {
#[derive(Debug, PartialEq, Clone)]
pub struct Paragraph {
num_leaves: u32,
text_align: u8,
text_decoration: u8,
text_direction: u8,
@ -204,6 +210,7 @@ pub struct Paragraph {
impl Default for Paragraph {
fn default() -> Self {
Self {
num_leaves: 0,
text_align: 0,
text_decoration: 0,
text_direction: 0,
@ -218,9 +225,9 @@ impl Default for Paragraph {
}
impl Paragraph {
// FIXME: These arguments could be grouped or simplified
#[allow(clippy::too_many_arguments)]
pub fn new(
num_leaves: u32,
text_align: u8,
text_decoration: u8,
text_direction: u8,
@ -232,6 +239,7 @@ impl Paragraph {
children: Vec<TextLeaf>,
) -> Self {
Self {
num_leaves,
text_align,
text_decoration,
text_direction,
@ -286,6 +294,7 @@ pub struct TextLeaf {
font_style: u8,
font_weight: i32,
font_variant_id: Uuid,
fills: Vec<shapes::Fill>,
}
impl TextLeaf {
@ -296,6 +305,7 @@ impl TextLeaf {
font_style: u8,
font_weight: i32,
font_variant_id: Uuid,
fills: Vec<shapes::Fill>,
) -> Self {
Self {
text,
@ -304,12 +314,26 @@ impl TextLeaf {
font_style,
font_weight,
font_variant_id,
fills,
}
}
pub fn to_style(&self, paragraph: &Paragraph) -> skia::textlayout::TextStyle {
pub fn to_style(
&self,
paragraph: &Paragraph,
content_bounds: &Rect,
) -> skia::textlayout::TextStyle {
let mut style = skia::textlayout::TextStyle::default();
style.set_color(skia::Color::BLACK);
let bounding_box = Rect::from_xywh(
content_bounds.x(),
content_bounds.y(),
self.font_size * self.text.len() as f32,
self.font_size,
);
let paint = merge_fills(&self.fills, bounding_box);
style.set_foreground_paint(&paint);
style.set_font_size(self.font_size);
style.set_letter_spacing(paragraph.letter_spacing);
style.set_height(paragraph.line_height);
@ -336,7 +360,7 @@ impl TextLeaf {
paragraph: &Paragraph,
stroke_paint: &Paint,
) -> skia::textlayout::TextStyle {
let mut style = self.to_style(paragraph);
let mut style = self.to_style(paragraph, &Rect::default());
style.set_foreground_paint(stroke_paint);
style
}
@ -366,11 +390,44 @@ impl TextLeaf {
}
}
pub const RAW_PARAGRAPH_DATA_SIZE: usize = 48;
pub const RAW_LEAF_DATA_SIZE: usize = 52;
const RAW_PARAGRAPH_DATA_SIZE: usize = std::mem::size_of::<RawParagraphData>();
//const RAW_LEAF_DATA_SIZE: usize = std::mem::size_of::<RawTextLeaf>();
// FIXME
pub const RAW_LEAF_DATA_SIZE: usize = 56;
pub const RAW_LEAF_FILLS_SIZE: usize = 160;
#[repr(C)]
#[derive(Debug)]
#[derive(Debug, Clone, Copy)]
pub struct RawTextLeaf {
font_style: u8,
font_size: f32,
font_weight: i32,
font_id: [u32; 4],
font_family: [u8; 4],
font_variant_id: [u32; 4],
text_length: u32,
total_fills: u32,
}
impl From<[u8; RAW_LEAF_DATA_SIZE]> for RawTextLeaf {
fn from(bytes: [u8; RAW_LEAF_DATA_SIZE]) -> Self {
unsafe { std::mem::transmute(bytes) }
}
}
impl TryFrom<&[u8]> for RawTextLeaf {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_LEAF_DATA_SIZE] = bytes
.get(0..RAW_LEAF_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid text leaf data".to_string())?;
Ok(RawTextLeaf::from(data))
}
}
#[allow(dead_code)]
#[derive(Debug, Clone)]
pub struct RawTextLeafData {
font_style: u8,
font_size: f32,
@ -379,15 +436,48 @@ pub struct RawTextLeafData {
font_family: [u8; 4],
font_variant_id: [u32; 4],
text_length: u32,
total_fills: u32,
fills: Vec<shapes::Fill>,
}
impl From<&[u8]> for RawTextLeafData {
fn from(bytes: &[u8]) -> Self {
let text_leaf: RawTextLeaf = RawTextLeaf::try_from(bytes).unwrap();
let total_fills = text_leaf.total_fills as usize;
// Use checked_mul to prevent overflow
let fills_size = total_fills
.checked_mul(RAW_LEAF_FILLS_SIZE)
.expect("Overflow occurred while calculating fills size");
let fills_start = RAW_LEAF_DATA_SIZE;
let fills_end = fills_start + fills_size;
let buffer = &bytes[fills_start..fills_end];
let fills = parse_fills_from_bytes(buffer, total_fills);
Self {
font_style: text_leaf.font_style,
font_size: text_leaf.font_size,
font_weight: text_leaf.font_weight,
font_id: text_leaf.font_id,
font_family: text_leaf.font_family,
font_variant_id: text_leaf.font_variant_id,
text_length: text_leaf.text_length,
total_fills: text_leaf.total_fills,
fills,
}
}
}
#[repr(C)]
#[derive(Debug)]
#[repr(align(4))]
#[derive(Debug, Clone, Copy)]
pub struct RawParagraphData {
num_leaves: u32,
text_align: u8,
text_transform: u8,
text_decoration: u8,
text_direction: u8,
text_decoration: u8,
text_transform: u8,
line_height: f32,
letter_spacing: f32,
typography_ref_file: [u32; 4],
@ -396,54 +486,22 @@ pub struct RawParagraphData {
impl From<[u8; RAW_PARAGRAPH_DATA_SIZE]> for RawParagraphData {
fn from(bytes: [u8; RAW_PARAGRAPH_DATA_SIZE]) -> Self {
Self {
text_align: bytes[4],
text_direction: bytes[5],
text_decoration: bytes[6],
text_transform: bytes[7],
line_height: f32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
letter_spacing: f32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
typography_ref_file: [
u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]),
u32::from_be_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
u32::from_be_bytes([bytes[28], bytes[29], bytes[30], bytes[31]]),
],
typography_ref_id: [
u32::from_be_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
u32::from_be_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]),
u32::from_be_bytes([bytes[40], bytes[41], bytes[42], bytes[43]]),
u32::from_be_bytes([bytes[44], bytes[45], bytes[46], bytes[47]]),
],
}
unsafe { std::mem::transmute(bytes) }
}
}
pub struct RawTextData {
pub paragraph: Paragraph,
impl TryFrom<&[u8]> for RawParagraphData {
type Error = String;
fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
let data: [u8; RAW_PARAGRAPH_DATA_SIZE] = bytes
.get(0..RAW_PARAGRAPH_DATA_SIZE)
.and_then(|slice| slice.try_into().ok())
.ok_or("Invalid paragraph data".to_string())?;
Ok(RawParagraphData::from(data))
}
}
impl RawTextData {
fn leaves_attrs_from_bytes(buffer: &[u8], num_leaves: usize) -> Vec<RawTextLeafData> {
let mut attrs = Vec::new();
for i in 0..num_leaves {
let start = i * RAW_LEAF_DATA_SIZE;
let end = start + RAW_LEAF_DATA_SIZE;
let bytes = &buffer[start..end];
let array: [u8; RAW_LEAF_DATA_SIZE] = bytes.try_into().expect("Slice length mismatch");
let leaf_attrs = RawTextLeafData::from(array);
attrs.push(leaf_attrs);
}
attrs
}
fn paragraph_attrs_from_bytes(buffer: &[u8]) -> RawParagraphData {
let bytes: [u8; RAW_PARAGRAPH_DATA_SIZE] = buffer[..RAW_PARAGRAPH_DATA_SIZE]
.try_into()
.expect("Slice length mismatch for paragraph attributes");
RawParagraphData::from(bytes)
}
fn text_from_bytes(buffer: &[u8], offset: usize, text_length: u32) -> (String, usize) {
let text_length = text_length as usize;
let text_end = offset + text_length;
@ -467,75 +525,60 @@ impl RawTextData {
}
}
impl From<[u8; RAW_LEAF_DATA_SIZE]> for RawTextLeafData {
fn from(bytes: [u8; RAW_LEAF_DATA_SIZE]) -> Self {
Self {
font_style: bytes[0],
font_size: f32::from_be_bytes([bytes[4], bytes[5], bytes[6], bytes[7]]),
font_weight: i32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]),
font_id: [
u32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]),
u32::from_be_bytes([bytes[16], bytes[17], bytes[18], bytes[19]]),
u32::from_be_bytes([bytes[20], bytes[21], bytes[22], bytes[23]]),
u32::from_be_bytes([bytes[24], bytes[25], bytes[26], bytes[27]]),
],
font_family: [bytes[28], bytes[29], bytes[30], bytes[31]],
font_variant_id: [
u32::from_be_bytes([bytes[32], bytes[33], bytes[34], bytes[35]]),
u32::from_be_bytes([bytes[36], bytes[37], bytes[38], bytes[39]]),
u32::from_be_bytes([bytes[40], bytes[41], bytes[42], bytes[43]]),
u32::from_be_bytes([bytes[44], bytes[45], bytes[46], bytes[47]]),
],
text_length: u32::from_be_bytes([bytes[48], bytes[49], bytes[50], bytes[51]]),
}
}
pub struct RawTextData {
pub paragraph: Paragraph,
}
impl From<&Vec<u8>> for RawTextData {
fn from(bytes: &Vec<u8>) -> Self {
let num_leaves = u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]) as usize;
let paragraph_attrs =
RawTextData::paragraph_attrs_from_bytes(&bytes[..RAW_PARAGRAPH_DATA_SIZE]);
let leaves_attrs =
RawTextData::leaves_attrs_from_bytes(&bytes[1 + RAW_PARAGRAPH_DATA_SIZE..], num_leaves);
let metadata_size = 1 + RAW_PARAGRAPH_DATA_SIZE + num_leaves * RAW_LEAF_DATA_SIZE;
let text_start = metadata_size;
let mut offset = text_start;
let paragraph = RawParagraphData::try_from(&bytes[..RAW_PARAGRAPH_DATA_SIZE]).unwrap();
let mut offset = RAW_PARAGRAPH_DATA_SIZE;
let mut raw_text_leaves: Vec<RawTextLeafData> = Vec::new();
let mut text_leaves: Vec<TextLeaf> = Vec::new();
for attrs in leaves_attrs {
let (text, new_offset) = RawTextData::text_from_bytes(bytes, offset, attrs.text_length);
offset = new_offset;
let font_id = uuid_from_u32(attrs.font_id);
let font_variant_id = uuid_from_u32(attrs.font_variant_id);
let font_family =
FontFamily::new(font_id, attrs.font_weight as u32, attrs.font_style.into());
let text_leaf = TextLeaf::new(
text,
font_family,
attrs.font_size,
attrs.font_style,
attrs.font_weight,
font_variant_id,
);
text_leaves.push(text_leaf);
for _ in 0..paragraph.num_leaves {
let text_leaf = RawTextLeafData::from(&bytes[offset..]);
raw_text_leaves.push(text_leaf.clone());
offset += RAW_LEAF_DATA_SIZE + (text_leaf.total_fills as usize * RAW_LEAF_FILLS_SIZE);
}
let typography_ref_file = uuid_from_u32(paragraph_attrs.typography_ref_file);
let typography_ref_id = uuid_from_u32(paragraph_attrs.typography_ref_id);
for text_leaf in raw_text_leaves.iter() {
let (text, new_offset) =
RawTextData::text_from_bytes(bytes, offset, text_leaf.text_length);
offset = new_offset;
let font_id = uuid_from_u32(text_leaf.font_id);
let font_variant_id = uuid_from_u32(text_leaf.font_variant_id);
let font_family = FontFamily::new(
font_id,
text_leaf.font_weight as u32,
text_leaf.font_style.into(),
);
let new_text_leaf = TextLeaf::new(
text,
font_family,
text_leaf.font_size,
text_leaf.font_style,
text_leaf.font_weight,
font_variant_id,
text_leaf.fills.clone(),
);
text_leaves.push(new_text_leaf);
}
let typography_ref_file = uuid_from_u32(paragraph.typography_ref_file);
let typography_ref_id = uuid_from_u32(paragraph.typography_ref_id);
let paragraph = Paragraph::new(
paragraph_attrs.text_align,
paragraph_attrs.text_decoration,
paragraph_attrs.text_direction,
paragraph_attrs.text_transform,
paragraph_attrs.line_height,
paragraph_attrs.letter_spacing,
paragraph.num_leaves,
paragraph.text_align,
paragraph.text_direction,
paragraph.text_decoration,
paragraph.text_transform,
paragraph.line_height,
paragraph.letter_spacing,
typography_ref_file,
typography_ref_id,
text_leaves.clone(),
@ -557,3 +600,54 @@ pub fn auto_height(paragraphs: &[Vec<skia::textlayout::Paragraph>]) -> f32 {
.flatten()
.fold(0.0, |auto_height, p| auto_height + p.height())
}
fn get_text_stroke_paints(stroke: &Stroke, bounds: &Rect) -> Vec<Paint> {
let mut paints = Vec::new();
match stroke.kind {
StrokeKind::Inner => {
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::DstOver);
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::SrcATop);
paint.set_anti_alias(true);
paint.set_stroke_width(stroke.width * 2.0);
set_paint_fill(&mut paint, &stroke.fill, bounds);
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);
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);
paints.push(paint);
let mut paint = skia::Paint::default();
paint.set_blend_mode(skia::BlendMode::Clear);
paint.set_anti_alias(true);
paints.push(paint);
}
}
paints
}

View file

@ -1,4 +1,7 @@
use crate::skia::Image;
use crate::uuid::Uuid;
use crate::with_state;
use crate::STATE;
pub fn uuid_from_u32_quartet(a: u32, b: u32, c: u32, d: u32) -> Uuid {
let hi: u64 = ((a as u64) << 32) | b as u64;
@ -18,3 +21,7 @@ pub fn uuid_to_u32_quartet(id: &Uuid) -> (u32, u32, u32, u32) {
pub fn uuid_from_u32(id: [u32; 4]) -> Uuid {
uuid_from_u32_quartet(id[0], id[1], id[2], id[3])
}
pub fn get_image(image_id: &Uuid) -> Option<&Image> {
with_state!(state, { state.render_state().images.get(image_id) })
}

View file

@ -53,6 +53,18 @@ impl TryFrom<&[u8]> for RawFillData {
}
}
pub fn parse_fills_from_bytes(buffer: &[u8], num_fills: usize) -> Vec<shapes::Fill> {
buffer
.chunks_exact(RAW_FILL_DATA_SIZE)
.take(num_fills)
.map(|bytes| {
RawFillData::try_from(bytes)
.expect("Invalid fill data")
.into()
})
.collect()
}
#[no_mangle]
pub extern "C" fn add_shape_fill() {
with_current_shape!(state, |shape: &mut Shape| {