Skip to content

Commit b2eccf5

Browse files
committed
Add simple shape API
1 parent be51bb8 commit b2eccf5

File tree

4 files changed

+116
-95
lines changed

4 files changed

+116
-95
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ siphasher = { workspace = true }
5858
skrifa = { workspace = true }
5959
tiny-skia = { workspace = true }
6060
subsetter = { workspace = true }
61+
rustybuzz = { workspace = true }
6162
tiny-skia-path = { workspace = true }
6263
usvg = { workspace = true }
6364
flate2 = { workspace = true }
@@ -68,7 +69,6 @@ float-cmp = { workspace = true }
6869
[dev-dependencies]
6970
difference = { workspace = true }
7071
paste = { workspace = true }
71-
rustybuzz = { workspace = true }
7272
sitro = { workspace = true }
7373
cosmic-text = { workspace = true }
7474
krilla-macros = { workspace = true }

src/surface.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ use crate::stream::{ContentBuilder, Glyph, Stream};
1010
use crate::{Fill, FillRule, Stroke};
1111
use fontdb::{Database, ID};
1212
use pdf_writer::types::BlendMode;
13+
use rustybuzz::{Direction, Feature, UnicodeBuffer};
14+
use skrifa::GlyphId;
1315
use std::collections::HashMap;
1416
use tiny_skia_path::{Path, Point, Size, Transform};
1517
use usvg::NormalizedF32;
@@ -80,6 +82,38 @@ impl<'a> Surface<'a> {
8082
.fill_glyphs(start, self.sc, fill, glyphs, font, text);
8183
}
8284

85+
pub fn fill_text<T>(
86+
&mut self,
87+
start: Point,
88+
fill: Fill<T>,
89+
font: Font,
90+
font_size: f32,
91+
features: &[Feature],
92+
text: &str,
93+
) where
94+
T: ColorSpace,
95+
{
96+
let glyphs = naive_shape(text, font.clone(), features, font_size);
97+
98+
self.fill_glyphs(start, fill, &glyphs, font, text);
99+
}
100+
101+
pub fn stroke_text<T>(
102+
&mut self,
103+
start: Point,
104+
stroke: Stroke<T>,
105+
font: Font,
106+
font_size: f32,
107+
features: &[Feature],
108+
text: &str,
109+
) where
110+
T: ColorSpace,
111+
{
112+
let glyphs = naive_shape(text, font.clone(), features, font_size);
113+
114+
self.stroke_glyphs(start, stroke, &glyphs, font, text);
115+
}
116+
83117
pub fn stroke_glyphs<T>(
84118
&mut self,
85119
start: Point,
@@ -219,6 +253,79 @@ impl Drop for Surface<'_> {
219253
}
220254
}
221255

256+
fn naive_shape(text: &str, font: Font, features: &[Feature], size: f32) -> Vec<Glyph> {
257+
let data = font.font_data();
258+
let rb_font = rustybuzz::Face::from_slice(data.as_ref().as_ref(), font.index()).unwrap();
259+
260+
let mut buffer = UnicodeBuffer::new();
261+
buffer.push_str(text);
262+
buffer.guess_segment_properties();
263+
264+
let dir = buffer.direction();
265+
266+
let output = rustybuzz::shape(&rb_font, features, buffer);
267+
268+
let positions = output.glyph_positions();
269+
let infos = output.glyph_infos();
270+
271+
let mut glyphs = vec![];
272+
273+
for i in 0..output.len() {
274+
let pos = positions[i];
275+
let start_info = infos[i];
276+
277+
let start = start_info.cluster as usize;
278+
279+
let end = if dir == Direction::LeftToRight {
280+
let mut e = i.checked_add(1);
281+
loop {
282+
if let Some(index) = e {
283+
if let Some(end_info) = infos.get(index) {
284+
if end_info.cluster == start_info.cluster {
285+
e = index.checked_add(1);
286+
continue;
287+
}
288+
}
289+
}
290+
291+
break;
292+
}
293+
294+
e
295+
} else {
296+
let mut e = i.checked_sub(1);
297+
loop {
298+
if let Some(index) = e {
299+
if let Some(end_info) = infos.get(index) {
300+
if end_info.cluster == start_info.cluster {
301+
e = index.checked_sub(1);
302+
} else {
303+
break;
304+
}
305+
}
306+
} else {
307+
break;
308+
}
309+
}
310+
311+
e
312+
}
313+
.and_then(|last| infos.get(last))
314+
.map_or(text.len(), |info| info.cluster as usize);
315+
316+
glyphs.push(Glyph::new(
317+
GlyphId::new(start_info.glyph_id),
318+
(pos.x_advance as f32 / font.units_per_em()) * size,
319+
(pos.x_offset as f32 / font.units_per_em()) * size,
320+
(pos.y_offset as f32 / font.units_per_em()) * size,
321+
start..end,
322+
size,
323+
));
324+
}
325+
326+
glyphs
327+
}
328+
222329
pub struct PageBuilder<'a> {
223330
sc: &'a mut SerializerContext,
224331
size: Size,

src/tests/manual.rs

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,11 @@ use crate::rgb::Rgb;
77
use crate::serialize::SerializeSettings;
88
use crate::stream::Glyph;
99
use crate::tests::{
10-
simple_shape, write_manual_to_store, ASSETS_PATH, COLR_TEST_GLYPHS, DEJAVU_SANS_MONO,
11-
NOTO_SANS, NOTO_SANS_ARABIC, NOTO_SANS_CJK, NOTO_SANS_DEVANAGARI,
10+
write_manual_to_store, ASSETS_PATH, COLR_TEST_GLYPHS, DEJAVU_SANS_MONO, NOTO_SANS,
11+
NOTO_SANS_ARABIC, NOTO_SANS_CJK, NOTO_SANS_DEVANAGARI,
1212
};
1313
use crate::util::SliceExt;
1414
use crate::Fill;
15-
use rustybuzz::Direction;
1615
use skrifa::instance::{Location, LocationRef, Size};
1716
use skrifa::raw::TableProvider;
1817
use skrifa::{GlyphId, MetadataProvider};
@@ -28,38 +27,28 @@ fn simple_shape_demo() {
2827
(
2928
NOTO_SANS_ARABIC.clone(),
3029
"هذا نص أطول لتجربة القدرات.",
31-
Direction::RightToLeft,
3230
14.0,
3331
),
3432
(
3533
NOTO_SANS.clone(),
3634
"Hi there, this is a very simple test!",
37-
Direction::LeftToRight,
3835
14.0,
3936
),
4037
(
4138
DEJAVU_SANS_MONO.clone(),
4239
"Here with a mono font, some longer text.",
43-
Direction::LeftToRight,
4440
16.0,
4541
),
46-
(NOTO_SANS.clone(), "z͈̤̭͖̉͑́a̳ͫ́̇͑̽͒ͯlͨ͗̍̀̍̔̀ģ͔̫̫̄o̗̠͔̦̳͆̏̓͢", Direction::LeftToRight, 14.0),
47-
(
48-
NOTO_SANS.clone(),
49-
" birth\u{ad}day ",
50-
Direction::LeftToRight,
51-
14.0,
52-
),
42+
(NOTO_SANS.clone(), "z͈̤̭͖̉͑́a̳ͫ́̇͑̽͒ͯlͨ͗̍̀̍̔̀ģ͔̫̫̄o̗̠͔̦̳͆̏̓͢", 14.0),
43+
(NOTO_SANS.clone(), " birth\u{ad}day ", 14.0),
5344
(
5445
NOTO_SANS_CJK.clone(),
5546
"你好世界,这是一段很长的测试文章",
56-
Direction::LeftToRight,
5747
14.0,
5848
),
5949
(
6050
NOTO_SANS_DEVANAGARI.clone(),
6151
"आ रु॒क्मैरा यु॒धा नर॑ ऋ॒ष्वा ऋ॒ष्टीर॑सृक्षत ।",
62-
Direction::LeftToRight,
6352
14.0,
6453
),
6554
];
@@ -68,14 +57,14 @@ fn simple_shape_demo() {
6857
let mut builder = document_builder.start_page(page_size);
6958
let mut surface = builder.surface();
7059

71-
for (font, text, dir, size) in data {
60+
for (font, text, size) in data {
7261
let font = Font::new(font.clone(), 0, Location::default()).unwrap();
73-
let glyphs = simple_shape(text, dir, font.clone(), size);
74-
surface.fill_glyphs(
62+
surface.fill_text(
7563
Point::from_xy(0.0, y),
7664
Fill::<Rgb>::default(),
77-
&glyphs,
7865
font,
66+
size,
67+
&[],
7968
text,
8069
);
8170

src/tests/mod.rs

Lines changed: 0 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,10 @@
1-
use crate::font::Font;
2-
use crate::stream::Glyph;
31
use difference::{Changeset, Difference};
42
use image::{load_from_memory, Rgba, RgbaImage};
53
use oxipng::{InFile, OutFile};
6-
use rustybuzz::{Direction, UnicodeBuffer};
74
use sitro::{
85
render_ghostscript, render_mupdf, render_pdfbox, render_pdfium, render_pdfjs, render_poppler,
96
render_quartz, RenderOptions, RenderedDocument, RenderedPage, Renderer,
107
};
11-
use skrifa::GlyphId;
128
use std::cmp::max;
139
use std::env;
1410
use std::path::PathBuf;
@@ -238,77 +234,6 @@ pub fn check_render(
238234
}
239235
}
240236

241-
pub fn simple_shape(text: &str, dir: Direction, font: Font, size: f32) -> Vec<Glyph> {
242-
let data = font.font_data();
243-
let rb_font = rustybuzz::Face::from_slice(data.as_ref().as_ref(), 0).unwrap();
244-
245-
let mut buffer = UnicodeBuffer::new();
246-
buffer.push_str(text);
247-
buffer.set_direction(dir);
248-
249-
let output = rustybuzz::shape(&rb_font, &[], buffer);
250-
251-
let positions = output.glyph_positions();
252-
let infos = output.glyph_infos();
253-
254-
let mut glyphs = vec![];
255-
256-
for i in 0..output.len() {
257-
let pos = positions[i];
258-
let start_info = infos[i];
259-
260-
let start = start_info.cluster as usize;
261-
262-
let end = if dir == Direction::LeftToRight {
263-
let mut e = i.checked_add(1);
264-
loop {
265-
if let Some(index) = e {
266-
if let Some(end_info) = infos.get(index) {
267-
if end_info.cluster == start_info.cluster {
268-
e = index.checked_add(1);
269-
continue;
270-
}
271-
}
272-
}
273-
274-
break;
275-
}
276-
277-
e
278-
} else {
279-
let mut e = i.checked_sub(1);
280-
loop {
281-
if let Some(index) = e {
282-
if let Some(end_info) = infos.get(index) {
283-
if end_info.cluster == start_info.cluster {
284-
e = index.checked_sub(1);
285-
} else {
286-
break;
287-
}
288-
}
289-
} else {
290-
break;
291-
}
292-
}
293-
294-
e
295-
}
296-
.and_then(|last| infos.get(last))
297-
.map_or(text.len(), |info| info.cluster as usize);
298-
299-
glyphs.push(Glyph::new(
300-
GlyphId::new(start_info.glyph_id),
301-
(pos.x_advance as f32 / font.units_per_em() as f32) * size,
302-
(pos.x_offset as f32 / font.units_per_em() as f32) * size,
303-
(pos.y_offset as f32 / font.units_per_em() as f32) * size,
304-
start..end,
305-
size,
306-
));
307-
}
308-
309-
glyphs
310-
}
311-
312237
pub fn render_document(doc: &[u8], renderer: &Renderer) -> RenderedDocument {
313238
let options = RenderOptions { scale: 1.0 };
314239

0 commit comments

Comments
 (0)