From 63a7eaedd08f97358f9b239c39a8a0df67b7af2e Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 02:24:16 +0300 Subject: [PATCH 01/13] add bbox start coord --- src/drawing/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index cf126df8..67bc0a4e 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -28,7 +28,7 @@ fn layout_glyphs( } last = Some(glyph_id); let bb = g.px_bounds(); - h = h.max(bb.height()); + h = h.max(bb.min.y + bb.height()); f(g, bb); } } From 5d7ef4d7a5d2058266aad90dd163e027ba198729 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 03:05:48 +0300 Subject: [PATCH 02/13] fix font size --- src/drawing/text.rs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 67bc0a4e..bd9ecb11 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -13,32 +13,29 @@ fn layout_glyphs( text: &str, mut f: impl FnMut(OutlinedGlyph, Rect), ) -> (u32, u32) { - let (mut w, mut h) = (0f32, 0f32); - let font = font.as_scaled(scale); - let mut last: Option = None; + + let mut w = 0f32; + let mut prev: Option = None; for c in text.chars() { let glyph_id = font.glyph_id(c); let glyph = glyph_id.with_scale_and_position(scale, point(w, font.ascent())); w += font.h_advance(glyph_id); if let Some(g) = font.outline_glyph(glyph) { - if let Some(last) = last { - w += font.kern(glyph_id, last); + if let Some(prev) = prev { + w += font.kern(glyph_id, prev); } - last = Some(glyph_id); + prev = Some(glyph_id); let bb = g.px_bounds(); - h = h.max(bb.min.y + bb.height()); f(g, bb); } } - (w as u32, h as u32) + (w as u32, font.height() as u32) } /// Get the width and height of the given text, rendered with the given font and scale. -/// -/// Note that this function *does not* support newlines, you must do this manually. pub fn text_size(scale: impl Into + Copy, font: &impl Font, text: &str) -> (u32, u32) { layout_glyphs(scale, font, text, |_, _| {}) } From 2bbf14f3c4d8d112814520ad83d7e6dcc5a2cd20 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 03:36:47 +0300 Subject: [PATCH 03/13] add stable clippy --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5db057c8..5864bd05 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -44,7 +44,7 @@ jobs: strategy: fail-fast: false matrix: - toolchain: [nightly] + toolchain: [stable, nightly] steps: - uses: actions/checkout@v4 with: From 23a10ab852b58be28e3fb76299a2f6098cdf130b Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 03:38:55 +0300 Subject: [PATCH 04/13] rm stable clippy --- .github/workflows/check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index 5864bd05..5db057c8 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -44,7 +44,7 @@ jobs: strategy: fail-fast: false matrix: - toolchain: [stable, nightly] + toolchain: [nightly] steps: - uses: actions/checkout@v4 with: From 38ce60148e728aa3a374a940aaea3ede56ba95dc Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 17:27:13 +0300 Subject: [PATCH 05/13] check text_size in regression test --- tests/regression.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/tests/regression.rs b/tests/regression.rs index a8e478df..0be7fa0a 100644 --- a/tests/regression.rs +++ b/tests/regression.rs @@ -23,9 +23,11 @@ use image::{ use imageproc::contrast::ThresholdType; use imageproc::definitions::Image; +use imageproc::drawing::text_size; use imageproc::filter::bilateral::GaussianEuclideanColorDistance; use imageproc::filter::bilateral_filter; use imageproc::kernel::{self}; +use imageproc::rect::{Rect, Region}; use imageproc::{ definitions::{Clamp, HasBlack, HasWhite}, edges::canny, @@ -808,19 +810,23 @@ fn test_bilateral_filter() { #[test] fn test_draw_text() { - let mut image = GrayImage::from_pixel(300, 300, Luma::black()); let font_bytes = include_bytes!("data/fonts/DejaVuSans.ttf"); let font = ab_glyph::FontRef::try_from_slice(font_bytes).unwrap(); - imageproc::drawing::draw_text_mut( - &mut image, - Luma::white(), - 50, - 100, - 30.0f32, - &font, - "Hello world!", - ); - - compare_to_truth_image(&image, "text.png"); + let background = Luma::black(); + let mut img = GrayImage::from_pixel(300, 300, background); + + let text = "Hello world!"; + let scale = 30.0; + let (x, y) = (50, 100); + imageproc::drawing::draw_text_mut(&mut img, Luma::white(), x, y, scale, &font, text); + compare_to_truth_image(&img, "text.png"); + + let (text_w, text_h) = text_size(scale, &font, text); + let rect = Rect::at(x, y).of_size(text_w, text_h); + for (px, py, &p) in img.enumerate_pixels() { + if !rect.contains(px as i32, py as i32) { + assert_eq!(p, background); + } + } } From f8d8b624e4951786bd01158388ba5a4dba26cb10 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 19:59:43 +0300 Subject: [PATCH 06/13] new text --- src/drawing/text.rs | 72 ++++++++++++++++++++++++++++++++------ tests/data/truth/text.png | Bin 3079 -> 2955 bytes 2 files changed, 62 insertions(+), 10 deletions(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index bd9ecb11..57b6e100 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -5,17 +5,22 @@ use crate::definitions::{Clamp, Image}; use crate::drawing::Canvas; use crate::pixelops::weighted_sum; -use ab_glyph::{point, Font, GlyphId, OutlinedGlyph, PxScale, Rect, ScaleFont}; +use ab_glyph::{point, Font, GlyphId, OutlinedGlyph, PxScale, ScaleFont}; fn layout_glyphs( scale: impl Into + Copy, font: &impl Font, text: &str, - mut f: impl FnMut(OutlinedGlyph, Rect), + mut f: impl FnMut(OutlinedGlyph, (i32, i32)), ) -> (u32, u32) { + if text.is_empty() { + return (0, 0); + } let font = font.as_scaled(scale); - let mut w = 0f32; + let first_char = text.chars().next().unwrap(); + let first_glyph = font.glyph_id(first_char); + let mut w = font.h_side_bearing(first_glyph).abs(); let mut prev: Option = None; for c in text.chars() { @@ -28,11 +33,17 @@ fn layout_glyphs( } prev = Some(glyph_id); let bb = g.px_bounds(); - f(g, bb); + let bbx = bb.min.x as i32; + let bby = bb.min.y as i32; + f(g, (bbx, bby)); } } - (w as u32, font.height() as u32) + let w = w.ceil(); + let h = font.height().ceil(); + assert!(w >= 0.0); + assert!(h >= 0.0); + (w as u32, h as u32) } /// Get the width and height of the given text, rendered with the given font and scale. @@ -64,6 +75,7 @@ where draw_text_mut(&mut out, color, x, y, scale, font, text); out } + #[doc=generate_mut_doc_comment!("draw_text")] pub fn draw_text_mut( canvas: &mut C, @@ -80,12 +92,12 @@ pub fn draw_text_mut( let image_width = canvas.width() as i32; let image_height = canvas.height() as i32; - layout_glyphs(scale, font, text, |g, bb| { - let bbx = x + bb.min.x.round() as i32; - let bby = y + bb.min.y.round() as i32; + layout_glyphs(scale, font, text, |g, (bbx, bby)| { + let x_shift = x + bbx; + let y_shift = y + bby; g.draw(|gx, gy, gv| { - let image_x = gx as i32 + bbx; - let image_y = gy as i32 + bby; + let image_x = gx as i32 + x_shift; + let image_y = gy as i32 + y_shift; if (0..image_width).contains(&image_x) && (0..image_height).contains(&image_y) { let image_x = image_x as u32; @@ -98,3 +110,43 @@ pub fn draw_text_mut( }) }); } + +//#[cfg(not(miri))] +#[cfg(test)] +mod proptests { + use super::*; + use crate::{ + proptest_utils::arbitrary_image_with, + rect::{Rect, Region}, + }; + use ab_glyph::FontRef; + use image::Luma; + use proptest::prelude::*; + + const FONT_BYTES: &[u8] = include_bytes!("../../tests/data/fonts/DejaVuSans.ttf"); + + proptest! { + #[test] + fn proptest_text_size( + x in 0..100, + y in 0..100, + scale in 0.0..100f32, + ref text in "[0-9a-zA-Z]*", + img in arbitrary_image_with::>(Just(0), 0..=100, 0..=100), + ) { + let font = FontRef::try_from_slice(FONT_BYTES).unwrap(); + let background = Luma([0]); + let text_color = Luma([255u8]); + + let img = draw_text(&img, text_color, x, y, scale, &font, text); + + let (text_w, text_h) = text_size(scale, &font, text); + let rect = Rect::at(x, y).of_size(1 + text_w, 1 + text_h); // TODO: fix Rect::contains + for (px, py, &p) in img.enumerate_pixels() { + if !rect.contains(px as i32, py as i32) { + assert_eq!(p, background, "pixel_position: {:?}, rect: {:?}", (px, py), rect); + } + } + } + } +} diff --git a/tests/data/truth/text.png b/tests/data/truth/text.png index 03d164a47fce1a740f0805314cbcc4e6bff54b89..dfeb60b53fa701085fe6f9b5bd7b056b7bc07c84 100644 GIT binary patch literal 2955 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 Date: Sat, 19 Oct 2024 20:00:36 +0300 Subject: [PATCH 07/13] ignore --- src/drawing/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 57b6e100..89b1f586 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -111,7 +111,7 @@ pub fn draw_text_mut( }); } -//#[cfg(not(miri))] +#[cfg(not(miri))] #[cfg(test)] mod proptests { use super::*; From 1204a13e2042e45e5f8c637ade4032f612dbf1b0 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 21:46:20 +0300 Subject: [PATCH 08/13] restore old impl --- src/drawing/text.rs | 37 ++++++++++++++++++++++++------------- tests/data/truth/text.png | Bin 2955 -> 2978 bytes 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 89b1f586..a5c036c6 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -5,22 +5,20 @@ use crate::definitions::{Clamp, Image}; use crate::drawing::Canvas; use crate::pixelops::weighted_sum; -use ab_glyph::{point, Font, GlyphId, OutlinedGlyph, PxScale, ScaleFont}; +use ab_glyph::{point, Font, GlyphId, OutlinedGlyph, PxScale, Rect, ScaleFont}; fn layout_glyphs( scale: impl Into + Copy, font: &impl Font, text: &str, - mut f: impl FnMut(OutlinedGlyph, (i32, i32)), + mut f: impl FnMut(OutlinedGlyph, Rect), ) -> (u32, u32) { if text.is_empty() { return (0, 0); } let font = font.as_scaled(scale); - let first_char = text.chars().next().unwrap(); - let first_glyph = font.glyph_id(first_char); - let mut w = font.h_side_bearing(first_glyph).abs(); + let mut w = 0.0; let mut prev: Option = None; for c in text.chars() { @@ -33,9 +31,7 @@ fn layout_glyphs( } prev = Some(glyph_id); let bb = g.px_bounds(); - let bbx = bb.min.x as i32; - let bby = bb.min.y as i32; - f(g, (bbx, bby)); + f(g, bb); } } @@ -92,9 +88,9 @@ pub fn draw_text_mut( let image_width = canvas.width() as i32; let image_height = canvas.height() as i32; - layout_glyphs(scale, font, text, |g, (bbx, bby)| { - let x_shift = x + bbx; - let y_shift = y + bby; + layout_glyphs(scale, font, text, |g, bb| { + let x_shift = x + bb.min.x.round() as i32; + let y_shift = y + bb.min.y.round() as i32; g.draw(|gx, gy, gv| { let image_x = gx as i32 + x_shift; let image_y = gy as i32 + y_shift; @@ -128,11 +124,11 @@ mod proptests { proptest! { #[test] fn proptest_text_size( + img in arbitrary_image_with::>(Just(0), 0..=100, 0..=100), x in 0..100, y in 0..100, scale in 0.0..100f32, ref text in "[0-9a-zA-Z]*", - img in arbitrary_image_with::>(Just(0), 0..=100, 0..=100), ) { let font = FontRef::try_from_slice(FONT_BYTES).unwrap(); let background = Luma([0]); @@ -141,7 +137,22 @@ mod proptests { let img = draw_text(&img, text_color, x, y, scale, &font, text); let (text_w, text_h) = text_size(scale, &font, text); - let rect = Rect::at(x, y).of_size(1 + text_w, 1 + text_h); // TODO: fix Rect::contains + // TODO: fix Rect::contains by making Rect a "closed set" + let (text_w, text_h) = (text_w + 1, text_h + 1); + + let rect = if text.is_empty() { + Rect::at(x, y).of_size(text_w, text_h) + } else { + let first_char = text.chars().next().unwrap(); + let first_x_bearing = + font.as_scaled(scale).h_side_bearing(font.glyph_id(first_char)); + if first_x_bearing < 0.0 { + let x_shift = first_x_bearing.abs().ceil() as i32; + Rect::at(x - x_shift, y).of_size(text_w, text_h) + } else { + Rect::at(x, y).of_size(text_w, text_h) + } + }; for (px, py, &p) in img.enumerate_pixels() { if !rect.contains(px as i32, py as i32) { assert_eq!(p, background, "pixel_position: {:?}, rect: {:?}", (px, py), rect); diff --git a/tests/data/truth/text.png b/tests/data/truth/text.png index dfeb60b53fa701085fe6f9b5bd7b056b7bc07c84..372749f1cb4d443ef3c36a4145920507029fbf0b 100644 GIT binary patch delta 1452 zcmW-geQXnT7{`CzaD~Bg5R{^0E6TfLbV%la^@Rwih&qxg71??qAp}s$EU8F_vz>S%HxGOxpsKtiV0T<>X~}o{oohOYsxzd*D4L>s?`O>zcC2jnCO*4b?Y0 z(3io>vgYOBr_#_>aAlHDE|G^%6|-Uf>vaOO`SG3-BH=_^(sQuYeCUR9P2>_+6&<%V z0Bd&*tn?Gi3d1Kvt5@5Wjo*lbggu@ScsE@yDPZ@4pXfK#7ZjD7b<1oCn9^DwS%jHx z0UlAPZ{MT8H)FhLm&3bLJb#Qm;LAa0slHWcR_ib|h5^+z;~p|7wxTIZm;Wd-{wziM zKZ^;{HjGHRvXkwj9k6!tmSSp5qP++|QCshqM0n`P7D3>YAd)Z#dBPs!)2G%6h-2oO zxzt-bV>|6Wrj>qZ77lmd)DH-m#m)w{$)qv7uSw9C#2Ic=F%k$H&<6N1kKLR~J}5k3 z$h(c+8UM~dM2|%7La*9)Y*-%ouisYuGO496?UiHjHmwRqk?HB>Dc zd!DMKpRQXD7o-1N6wQ%Ok_zUrtN{2U&lvwwog0uHL!;-&;dhWD{WOoWFlvdl-4jie z>oqQKZluvU@~hr9dcC3vS{t`(_$Y%5El_16LGHK1+LdNzyKqoJRod$v%oACt((Vgn z+cnMe`P3%2LUO{O*$1M%B?{pn*Yzjv6H;5@wl#%sGE&ZLdT^F6RM{*yJMZuHcj@K8 z{m@MPDzrFBJu_d&Z@OIM9zf1=MGNG5BKnRTx-I{T0CFRW#q$9YzHb!+Z9X*BNqeLq zx?b6lJ037*>bkMs5UdFtEta7RTZ$yL`w~rw>*{7xs*w0o zq)gR|L`Cjly3i0a^8<&0>LqAu8*`>-qS^l;Xq={@O=&6oJe5HX-@az3U8z~_K;5SY zRr4LAgqCzB&dn7xuqLlGH{e0pyY$~2YTF_2e|z9+NgsRB)%TnQo(T-Uh*Ae{i$S}i zKQ5!*HmDA@b=KxpJ`&m$)}U|fR*(M--ML8a_rrPue9*7l?8pR5bM0+b^0XWjh!EuX z|6j7vHKQcH9+h7)S?T=Jz{0xLbb6@?j)$UI(JvsRnXlS{q`OxX2|vrrZI`OWb>WAW z!yWm;{+fVsmd-6{|APqLweBhzDNNH{GX$k7ok?`rsSzRRNFMCV@=_(BuDgCa7&W&` zRpOK^AE?P8u}si{*7}F@@$Dy*Z2`5zUx=1>EJ8vuxH{FzG0hw!tHi~Cf${7=cSz!k5rKsAwQiDw8Rbwt&+xkC}ty{5HI9-uAg5HCr~nyXpFCyH5NIo_{t4 delta 1429 zcmW-gjZYJI7=W*QDk{|>>kQJW%*@C#oXX0^(IaE(l#jX*g9(h{NJf?b1q-$mPfalHoKT_m zrhVVF`-cF)MGqls;Sh^VVU2-iDRxsjQ_Cb%kg{ef01BeM0M4sg%XOyfYR5pdRuiQ} z>jMWIiK@L+X|B12*AV)Z^Tp}_WL%Ov`iV~hS{;1!a(;oRojg^D8mw<5C&&oe@xH}F zv}E%ij9{pN`nFzQX(Lvt`C^$WQy~PY)2^j{_+^ZzQcHsitr}XPVF-arW}M97B2_63 zVm>G(Wukiwgxmn0cf)TRSYk_Di&?eK&q}awW0eOyQ+WQ0*WRTZ^lO0IvBAq<$@^RAlj4#L7OZR%rHLF{sida;&A&%E?)#`=Q3Sy|JjONokWnq*1W; z<)C0eS!2KpcOV`<20pNV3V@VH7bO81FUxl92AO^#Zk~%gH*(1cgR zVu4)c{1cNC>+dc>WAI}T7RK}(he(sYqUHz!t|@}^sD4p;o{id^1MmHmR=o0uy#K+JHk2?D`%uwQ*e z)GC=#%|1|>uYcC10lT2R)OYvY_SN}nwmpaWRK#eU3ZZ+W{hS2+KY#-<5u&mWD%`po zieY{Ly>5bLJhOBPnj8#Fx|-_EIg_X@yO{JYVqE&8729uhYv9KC+Xo@>Nwlz+vvO16 zZd8o&ZP|E}Gyc(nur{(R6%uy^Xbp z#KQ)Xx##sW5A;J^6Ro-~3$$U6w^Y~ZE0d}tjGeY7$o9szRi@80=^ifY3dvY#GjRlO zsKg%)ZE&n1bF&-NL_VfNVKrYo3o`L?BU_|toMA&&UpnW0;9Jh`hW-|da}IH70!z?D zds>_`L^0?$d5+pME^(YzIrh|yv)l(@Dc6=?{ZmwiZ{*TSL`7>i(5Bj=)T{Kk_BJHI z7(p0$aJUB8calLexpqdUY#XWryG89>O1C=ut|4$u?g7|>lQjLTa#Z#jyk_Zq1z}=% z$E@CekiMmO-!?ERz&0g#h3Z$V!Bsj$=n7D=o3OK{P;u3u+(8hFdON28IJL61XIj(y z^v7^PD4dxnEZ(HfQqFVJ>3&1~_8E88LAA9nrlwBdg{l_&$uZ(YZWq;E(2MVqjVojb zB?!Tupcb$m!UlS&Q8JR(Ni7LIY0w#M^yw#L-&u&Wm}2F)JaO(hHgOxZMAY~M44fNO zgVB4+M|&Er16mlcllI6<8F9Cw_^*nDwy!50m-}V^f=Fn%o3^e#@XB#o7{kb-vCBG- Uj)(9MK>j&;DC3*^U+10s9}|Zoc>n+a From 3d4b962ff2f62478d659933cb43362ba8ad39d4a Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 21:48:35 +0300 Subject: [PATCH 09/13] text.png --- tests/data/truth/text.png | Bin 2978 -> 3079 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/data/truth/text.png b/tests/data/truth/text.png index 372749f1cb4d443ef3c36a4145920507029fbf0b..03d164a47fce1a740f0805314cbcc4e6bff54b89 100644 GIT binary patch literal 3079 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 From 62d5737936c76665c86d75a732fb59957782c3ad Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 21:53:10 +0300 Subject: [PATCH 10/13] add x_Shift --- src/drawing/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index a5c036c6..40a7eeab 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -148,7 +148,7 @@ mod proptests { font.as_scaled(scale).h_side_bearing(font.glyph_id(first_char)); if first_x_bearing < 0.0 { let x_shift = first_x_bearing.abs().ceil() as i32; - Rect::at(x - x_shift, y).of_size(text_w, text_h) + Rect::at(x - x_shift, y).of_size(text_w + x_shift as u32, text_h) } else { Rect::at(x, y).of_size(text_w, text_h) } From 945ceb265e814e12e7f83b1893efed83ba1e9ef1 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 22:20:16 +0300 Subject: [PATCH 11/13] add +1 --- src/drawing/text.rs | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 40a7eeab..7566f7a9 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -39,7 +39,7 @@ fn layout_glyphs( let h = font.height().ceil(); assert!(w >= 0.0); assert!(h >= 0.0); - (w as u32, h as u32) + (1 + w as u32, 1 + h as u32) } /// Get the width and height of the given text, rendered with the given font and scale. @@ -136,22 +136,19 @@ mod proptests { let img = draw_text(&img, text_color, x, y, scale, &font, text); + if text.is_empty() { + return Ok(()); + } let (text_w, text_h) = text_size(scale, &font, text); - // TODO: fix Rect::contains by making Rect a "closed set" - let (text_w, text_h) = (text_w + 1, text_h + 1); - let rect = if text.is_empty() { - Rect::at(x, y).of_size(text_w, text_h) + let first_char = text.chars().next().unwrap(); + let first_x_bearing = + font.as_scaled(scale).h_side_bearing(font.glyph_id(first_char)); + let rect = if first_x_bearing < 0.0 { + let x_shift = first_x_bearing.abs().ceil() as i32; + Rect::at(x - x_shift, y).of_size(text_w + x_shift as u32, text_h) } else { - let first_char = text.chars().next().unwrap(); - let first_x_bearing = - font.as_scaled(scale).h_side_bearing(font.glyph_id(first_char)); - if first_x_bearing < 0.0 { - let x_shift = first_x_bearing.abs().ceil() as i32; - Rect::at(x - x_shift, y).of_size(text_w + x_shift as u32, text_h) - } else { - Rect::at(x, y).of_size(text_w, text_h) - } + Rect::at(x, y).of_size(text_w, text_h) }; for (px, py, &p) in img.enumerate_pixels() { if !rect.contains(px as i32, py as i32) { From e2643c3d28fc141526e3702aef08ccbf108a7aa4 Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 22:33:31 +0300 Subject: [PATCH 12/13] add +1 only to width --- src/drawing/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 7566f7a9..0e1b1bd7 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -39,7 +39,7 @@ fn layout_glyphs( let h = font.height().ceil(); assert!(w >= 0.0); assert!(h >= 0.0); - (1 + w as u32, 1 + h as u32) + (1 + w as u32, h as u32) } /// Get the width and height of the given text, rendered with the given font and scale. From d1c69926ef6f8986834154031332e1134252e87b Mon Sep 17 00:00:00 2001 From: Alex Severin Date: Sat, 19 Oct 2024 22:34:26 +0300 Subject: [PATCH 13/13] call text_size on empty text as well --- src/drawing/text.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/drawing/text.rs b/src/drawing/text.rs index 0e1b1bd7..13319afb 100644 --- a/src/drawing/text.rs +++ b/src/drawing/text.rs @@ -136,10 +136,10 @@ mod proptests { let img = draw_text(&img, text_color, x, y, scale, &font, text); + let (text_w, text_h) = text_size(scale, &font, text); if text.is_empty() { return Ok(()); } - let (text_w, text_h) = text_size(scale, &font, text); let first_char = text.chars().next().unwrap(); let first_x_bearing =