From 435e102e526e206a89f6a1bae2e9f8a1d1646b9f Mon Sep 17 00:00:00 2001 From: LongYinan Date: Wed, 20 Oct 2021 14:07:03 +0800 Subject: [PATCH] fix: text align position --- __test__/snapshots/text-align-center.png | Bin 0 -> 3342 bytes __test__/snapshots/text-align-end.png | Bin 0 -> 3331 bytes __test__/snapshots/text-align-left.png | Bin 0 -> 3354 bytes __test__/snapshots/text-align-right.png | Bin 0 -> 3342 bytes __test__/snapshots/text-align-start.png | Bin 0 -> 3355 bytes __test__/text.spec.ts | 37 +++++++++++++++++++ skia-c/skia_c.cpp | 45 +++++++++++++++++++++-- skia-c/skia_c.hpp | 1 + src/ctx.rs | 8 ++-- src/error.rs | 10 +++++ src/sk.rs | 23 +++++++----- 11 files changed, 108 insertions(+), 16 deletions(-) create mode 100644 __test__/snapshots/text-align-center.png create mode 100644 __test__/snapshots/text-align-end.png create mode 100644 __test__/snapshots/text-align-left.png create mode 100644 __test__/snapshots/text-align-right.png create mode 100644 __test__/snapshots/text-align-start.png create mode 100644 __test__/text.spec.ts diff --git a/__test__/snapshots/text-align-center.png b/__test__/snapshots/text-align-center.png new file mode 100644 index 0000000000000000000000000000000000000000..c1c8b5bbdd8275d094f523b16e9a6c4706b805f2 GIT binary patch literal 3342 zcmeHK`%hDM6#r75GMR%obwaHZHeF`g7^BEaVK6o%%rcM-mlQi68yIK{Dbhkq>m$Bk zmYG-^S+Uua)QJ`cdRr_|)WMx1MZ~Fa`yf_nT)o^~3%4zO-1YC+IKP~8l9NwPPR_~a zb54E5&QFM69S;CVDA=A?0sti(Qb1fx*v*L_FN9r$up~bh=*%gv!by}cx1cmGY|^;> zCjnS$D9Fnx6{}H8`DK1X4vgEsJU97PN99L3$McHQmGwIlOQWK5_ME(wmit+>QrZ0Y zG$W_v@HdxAqBc*beUVpEY^^J{p3OaXwsh(KE$?nP`opTqj?Lv)Z=P>DzQ-aoXN?Ye z+=qu6F5D#C+_763E~D*2!sW&wl%6@xrHJ=he0lN+FimOzIJ7nbfShpmN20=AE&(7e z34nLf09d;nfKPu~ELfbwe|aDHqg4>Aj?ERbS7Kb@&?ed3qY4@R;W_&it-!D3k$q@8 zG9W-`_T%%QXdMOcN3BSx8fnoty;kp*nvMSEeSJbis1Lr_K{tKfHASd-_I`8}86dY_ zixnp&f|{zVZd9&kT}O91=LR#d)Up&uHe{8DB%4PBe-(<<-+F)MVO9b{ZmgLu$OD6@ z=P|F#MwB5oJ*sr}AzMP7G}tTROKi|GZkuPl5#KoI2}6U6Cx8JAI~I3Te`L3;|CO3C8FmWT~e|K$@(2F>#7Y1 zY(%8fugQQ-lc~NU^~5onolqjXyU`w~zZQW-mh>7Agngn4-huQ2|4Vm}<$S|H2nLhh zS`EJ)>Xmqt?(I8m;xU*iQzd*}Ni*ImF2inYDFLRw@%x{SNd*G#Q= zk4`_6`NYf2_Qx{m#epDXHw^QRZj1m=9XcLY#1@sek!N{~XM{jC6Lcx4FDf{98yUmo zbVma@fF^ex2%vGz9&RH!&eM=5O}>YzPe^qkW;db|sEJS%Mlvk5SI8?4atNYXYFE5G zz+89#baPD$hjPe;4=R%9AxGQ7F{W8=cW==23gJZjnqd|BOQCyB-!QIsY`RUe5maQL z5oUYGA!qjpju#Gcon#$-_=LforL zAr*ygy7@KB;KGNgo~Xbdii_@*)&4Uu?mGbm$DuAO&LV5lV@9;e(%cYNNI51Si4@_>B#m!>SIh2PUF-#d6cQ^l#h2b*df&B zo~JU|dV7y!bs%Wmv{m=F6&curq@%+Z{hdFO1drQ;nnawp#V$x-I)wqw^QFMjR#EML zz#E&otL@huG)smR$<|pbUTK4&;F!W_%Qm3pPP-gez{G_CRH$!yrD(UF&=~?YYU>Kw zoRqvz%^xW{h$(213tiOM=&q^S32S}ceMx<0qetO3_U(r+O2(K^eSf0znNc$+qWH=M7 zEQ;XFi@igozf^X(ln?(8N|NR0swcFVSxJ0J@0`nckM#(H7fM$D$71 zTHK_l=ia~M%!5k(#Q z4uGYdIawJDK?`Ooy$jW5=%%dA6`stb!=E&+D|(XOZe~QrW@PO9V13eshx;-r+Sk|E za~m1gVsFL_ETuNAyu@HoN*R>PjT<+{MjXn2=l-=-3lFRF;!dQEaF=aOuhjqCcHQlW zxO&@r*gz;$Tb4Qdo=Q4Zq)!!QPp^|$p`8`nsgVF+3$+1oW-SSTj4=HA$S}-z0f>4J zfE616Si2X1-Hr1J^L_X)&%+t#o8O@2t+Fk<65|xin!(h%=Z}PS{}lbMDJ?X5hEbvx zMG>(Trk`F4b9LeM=o(H#iLcQmu-GT+x0Ub=u{r?`S3-D0b`QZ*9YwbxVlPZ#38%`t zTWJLVRF|eJv2rxQ zKf$U~C=(KT;U@WTLdO)dc8Ogw4p*{qZF_~Jf3;zO$O0F$g@&FV}68^~AOjJN8pOV7F2XTAT-J+4qr{x3pQF>e3Th`{ zrin8Xa!va$x@^8gqlb$UJYCFyf`wneIRc-vP0F zI7O$ULqa#K*hzmJblmEYhK@LHA(P7OwrI2tdLMvYb)}Qd8a~A}17mi6b)YXLxsSSy zxMyCauyXRXGBw44FClF zb3W+-n|zdW&i2i;|8MtDs|}NezRw+2A4BEXSJ|ONG*vf9?fMdPVU{O{OoL zVbb&o{YQ0s_~^ZM0k755l|IW*q=IPZk!OG?>up1a*!vdg79I~%8osOH3JI|IwK zysW_vBxsg)_}D~iy<<}8CK7o8Ij+yOx<9L1>?aGUr&EOCk?Il}VXM(2qou@)o+XRu ww$M5f_@aw6e<0_1z&szC=Y{`Ue*98GV)VvDqBW$1@FN3q_7r4wWqw}&FD9vu5C8xG literal 0 HcmV?d00001 diff --git a/__test__/snapshots/text-align-left.png b/__test__/snapshots/text-align-left.png new file mode 100644 index 0000000000000000000000000000000000000000..86385d9295077eb9ca076cf68a75068a82b3fe4b GIT binary patch literal 3354 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 GR{sNGsgLRa literal 0 HcmV?d00001 diff --git a/__test__/snapshots/text-align-start.png b/__test__/snapshots/text-align-start.png new file mode 100644 index 0000000000000000000000000000000000000000..f4eba7bd3a25babb09b9fc3d97753d2c9646fe90 GIT binary patch literal 3355 zcmeHK`%hDM6#ptH6-C6MRk5hS%w+?bEiU3}K@kQ_1qJ4!@=_FJ^ir@wfkKPA=@_mm zz9xwEk-_3-I|OeleX@ylL#eC+!k1FEx^^hH7cI8*dDpoAV&nXB9y!VR zoDkr@%pU*{kdzpg3cyUSITMg(d(~w56NgvLV5KI+0%6bcF|XsxicLxy+HdD%j*$hr68Yl>*Ms1T4^#)lrY(w1-BYKpXqNmn}h+c_(1G!Trj!;k@ zE8##tadj08gV7b08xA#CJGbyHdKen*mY=taU&Pb+;Vw7IMmXJuNjjMLqIj?nTxrM7 zLr)o$isi1g*v*U>&wI>$E{)DQj;x4kwB}2#@2JQK=MieVtT6OJ#pqi6&)^3ZR9*l# zL+)O9R@BJ5k+yo>G9FYsTWj15Jvp%37(5HGAJZ>Z>9>fhO(-Wt#N$FW>UJ!OPr|1m3n6|o1a~qSPQF%Fm$p*LZm_L`BeJl>NF!lKMI*T_iHbb+ zj^lN2YPD!Qz5KLbq8$?pi*;bMxyer@hl;UDiH+f)!z)rOM$_z8^8=olGCDuVI7ukl z3Wz({9??M;M*7-zS?+m^QWa)8t~5IZ6;?+k(3rFpUuXD=mT#}!%F z^^EUHDhiA1KYE5jevYWuzn;r(@+d6YDvz1MxhFR4b0uh+tDpO;^?c~dV?Z0j>7a!C zks^*Z^%xAmr!!O93(=G} zXn6yJ?R8;q35NR0-TcCt+)DL`oh5cpZL9M1BD~vJ2s_-g0w2n;Z#Bf^LY(2m5Y3=< zNE^lMuzxzE2C^?nL3FEO!PFY|@>S(ildeWK_VL8d%#*~)wu0$3A)-k~2y2&v()s!?2RiwO4t8MPdg zUaG!zI7Od(LN4GoSf5bH9dwDIUlz!8Dk-1Il}t!W#)(%HF-AWC$wkiJC?Qf?_94$P zT2X}S@!0Eb_IN~g|1lbO-rr%g>&8X2$PUO)tT6HS;3H+bFObcWQhcxlmvP;KGM$5| z?y66UrZ8N7^(Q8$u$N?@jR}-ox%yP?9m-`H5ug$ zh%=oRDdd1??Ct)*&_El)(KTg}z-ZeRu<=_WV8QgooMsNw1ZkQm{$C06Rl?8G9PGPf T?ysA@4-QC*PmUAD<{bS8!2O(R literal 0 HcmV?d00001 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(