diff --git a/__test__/snapshots/text-align-center.png b/__test__/snapshots/text-align-center.png new file mode 100644 index 00000000..c1c8b5bb Binary files /dev/null and b/__test__/snapshots/text-align-center.png differ diff --git a/__test__/snapshots/text-align-end.png b/__test__/snapshots/text-align-end.png new file mode 100644 index 00000000..d3a79fc3 Binary files /dev/null and b/__test__/snapshots/text-align-end.png differ diff --git a/__test__/snapshots/text-align-left.png b/__test__/snapshots/text-align-left.png new file mode 100644 index 00000000..86385d92 Binary files /dev/null and b/__test__/snapshots/text-align-left.png differ diff --git a/__test__/snapshots/text-align-right.png b/__test__/snapshots/text-align-right.png new file mode 100644 index 00000000..4a72c926 Binary files /dev/null and b/__test__/snapshots/text-align-right.png differ diff --git a/__test__/snapshots/text-align-start.png b/__test__/snapshots/text-align-start.png new file mode 100644 index 00000000..f4eba7bd Binary files /dev/null and b/__test__/snapshots/text-align-start.png differ diff --git a/__test__/text.spec.ts b/__test__/text.spec.ts new file mode 100644 index 00000000..6daa4e83 --- /dev/null +++ b/__test__/text.spec.ts @@ -0,0 +1,37 @@ +import { readFileSync } from 'fs' +import { join } from 'path' + +import ava, { TestInterface } from 'ava' + +import { GlobalFonts, createCanvas, Canvas, SKRSContext2D } from '../index' +import { snapshotImage } from './image-snapshot' + +const test = ava as TestInterface<{ + canvas: Canvas + ctx: SKRSContext2D +}> + +const fontIosevka = readFileSync(join(__dirname, 'fonts', 'iosevka-slab-regular.ttf')) + +console.assert(GlobalFonts.register(fontIosevka), 'Register Iosevka font failed') + +test.beforeEach((t) => { + const canvas = createCanvas(512, 512) + t.context.canvas = canvas + t.context.ctx = canvas.getContext('2d')! +}) + +for (const align of ['center', 'end', 'left', 'right', 'start'] as CanvasTextAlign[]) { + test(`text-align-${align}`, async (t) => { + const { ctx, canvas } = t.context + const x = canvas.width / 2 + ctx.strokeStyle = 'black' + ctx.moveTo(x, 0) + ctx.lineTo(x, canvas.height) + ctx.stroke() + ctx.textAlign = align + ctx.font = '16px Iosevka Slab' + ctx.fillText('Hello Canvas', 0, 200) + await snapshotImage(t) + }) +} diff --git a/skia-c/skia_c.cpp b/skia-c/skia_c.cpp index 1ad7da25..170b9660 100644 --- a/skia-c/skia_c.cpp +++ b/skia-c/skia_c.cpp @@ -343,6 +343,7 @@ extern "C" float max_width, float x, float y, + float canvas_width, skiac_font_collection *c_collection, float font_size, int weight, @@ -358,6 +359,7 @@ extern "C" { auto font_collection = c_collection->collection; auto font_style = SkFontStyle(weight, stretch, (SkFontStyle::Slant)slant); + auto text_direction = (TextDirection)direction; SkTArray families; SkStrSplit(font_family, ",", &families); TextStyle text_style; @@ -380,8 +382,7 @@ extern "C" ParagraphStyle paragraph_style; paragraph_style.turnHintingOff(); paragraph_style.setTextStyle(text_style); - paragraph_style.setTextAlign((TextAlign)align); - paragraph_style.setTextDirection((TextDirection)direction); + paragraph_style.setTextDirection(text_direction); ParagraphBuilderImpl builder(paragraph_style, font_collection); builder.addText(text, text_len); auto paragraph = static_cast(builder.Build().release()); @@ -444,8 +445,44 @@ extern "C" if (c_canvas) { - auto align_factor = 0; - auto paint_x = x + line_width * align_factor; + auto canvas_center = canvas_width / 2.0f; + float paint_x; + switch ((TextAlign)align) + { + case TextAlign::kLeft: + paint_x = x + canvas_center; + break; + case TextAlign::kCenter: + paint_x = x + canvas_center - (line_width / 2); + break; + case TextAlign::kRight: + paint_x = x + canvas_center - line_width; + break; + // Unreachable + case TextAlign::kJustify: + paint_x = x; + break; + case TextAlign::kStart: + if (text_direction == TextDirection::kLtr) + { + paint_x = x; + } + else + { + paint_x = x + canvas_width - line_width; + } + break; + case TextAlign::kEnd: + if (text_direction == TextDirection::kRtl) + { + paint_x = x; + } + else + { + paint_x = x + canvas_width - line_width; + } + break; + }; auto need_scale = line_width > max_width; if (need_scale) { diff --git a/skia-c/skia_c.hpp b/skia-c/skia_c.hpp index 5e991311..a444081e 100644 --- a/skia-c/skia_c.hpp +++ b/skia-c/skia_c.hpp @@ -267,6 +267,7 @@ extern "C" float max_width, float x, float y, + float canvas_width, skiac_font_collection *c_collection, float font_size, int weight, diff --git a/src/ctx.rs b/src/ctx.rs index bcb2b72c..4b1d9f8d 100644 --- a/src/ctx.rs +++ b/src/ctx.rs @@ -662,6 +662,7 @@ impl Context { x, y, max_width, + self.width as f32, weight, stretch as i32, slant, @@ -672,7 +673,7 @@ impl Context { state.text_align, state.text_direction, &shadow_paint, - ); + )?; surface.restore(); } @@ -681,6 +682,7 @@ impl Context { x, y, max_width, + self.width as f32, weight, stretch as i32, slant, @@ -691,7 +693,7 @@ impl Context { state.text_align, state.text_direction, paint, - ); + )?; Ok(()) } @@ -713,7 +715,7 @@ impl Context { state.text_align, state.text_direction, &fill_paint, - )); + )?); Ok(line_metrics) } diff --git a/src/error.rs b/src/error.rs index c6fea91a..1748599c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,3 +1,5 @@ +use std::ffi::NulError; + use thiserror::Error; use crate::sk::Matrix; @@ -26,6 +28,14 @@ pub enum SkError { U32ToStrokeJoinError(u32), #[error("[`{0}`] is not valid transform")] InvalidTransform(Matrix), + #[error("Convert String to CString failed")] + NulError, #[error("[`{0}`]")] Generic(String), } + +impl From for SkError { + fn from(_: NulError) -> Self { + Self::NulError + } +} diff --git a/src/sk.rs b/src/sk.rs index 1c1a84ed..817dcc02 100644 --- a/src/sk.rs +++ b/src/sk.rs @@ -1,6 +1,6 @@ use std::convert::TryFrom; use std::f32::consts::PI; -use std::ffi::{c_void, CStr, CString}; +use std::ffi::{c_void, CStr, CString, NulError}; use std::fmt; use std::ops::{Deref, DerefMut}; use std::os::raw::c_char; @@ -359,6 +359,7 @@ mod ffi { max_width: f32, x: f32, y: f32, + canvas_width: f32, font_collection: *mut skiac_font_collection, font_size: f32, weight: i32, @@ -1975,6 +1976,7 @@ impl Canvas { x: f32, y: f32, max_width: f32, + canvas_width: f32, weight: u32, stretch: i32, slant: FontStyle, @@ -1985,9 +1987,9 @@ impl Canvas { align: TextAlign, direction: TextDirection, paint: &Paint, - ) { - let c_text = std::ffi::CString::new(text).unwrap(); - let c_font_family = std::ffi::CString::new(font_family).unwrap(); + ) -> Result<(), NulError> { + let c_text = std::ffi::CString::new(text)?; + let c_font_family = std::ffi::CString::new(font_family)?; unsafe { ffi::skiac_canvas_get_line_metrics_or_draw_text( @@ -1996,6 +1998,7 @@ impl Canvas { max_width, x, y, + canvas_width, font_collection.0, font_size, weight as i32, @@ -2009,7 +2012,8 @@ impl Canvas { self.0, ptr::null_mut(), ); - } + }; + Ok(()) } pub fn get_line_metrics( @@ -2025,9 +2029,9 @@ impl Canvas { align: TextAlign, direction: TextDirection, paint: &Paint, - ) -> ffi::skiac_line_metrics { - let c_text = std::ffi::CString::new(text).unwrap(); - let c_font_family = std::ffi::CString::new(font_family).unwrap(); + ) -> Result { + let c_text = std::ffi::CString::new(text)?; + let c_font_family = std::ffi::CString::new(font_family)?; let mut line_metrics = ffi::skiac_line_metrics::default(); @@ -2038,6 +2042,7 @@ impl Canvas { 0.0, 0.0, 0.0, + 0.0, font_collection.0, font_size, weight as i32, @@ -2052,7 +2057,7 @@ impl Canvas { &mut line_metrics, ); } - line_metrics + Ok(line_metrics) } pub fn draw_surface(