Skip to content

Commit

Permalink
Add simple shape API
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenzV committed Aug 30, 2024
1 parent be51bb8 commit b2eccf5
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 95 deletions.
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ siphasher = { workspace = true }
skrifa = { workspace = true }
tiny-skia = { workspace = true }
subsetter = { workspace = true }
rustybuzz = { workspace = true }
tiny-skia-path = { workspace = true }
usvg = { workspace = true }
flate2 = { workspace = true }
Expand All @@ -68,7 +69,6 @@ float-cmp = { workspace = true }
[dev-dependencies]
difference = { workspace = true }
paste = { workspace = true }
rustybuzz = { workspace = true }
sitro = { workspace = true }
cosmic-text = { workspace = true }
krilla-macros = { workspace = true }
Expand Down
107 changes: 107 additions & 0 deletions src/surface.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use crate::stream::{ContentBuilder, Glyph, Stream};
use crate::{Fill, FillRule, Stroke};
use fontdb::{Database, ID};
use pdf_writer::types::BlendMode;
use rustybuzz::{Direction, Feature, UnicodeBuffer};
use skrifa::GlyphId;
use std::collections::HashMap;
use tiny_skia_path::{Path, Point, Size, Transform};
use usvg::NormalizedF32;
Expand Down Expand Up @@ -80,6 +82,38 @@ impl<'a> Surface<'a> {
.fill_glyphs(start, self.sc, fill, glyphs, font, text);
}

pub fn fill_text<T>(
&mut self,
start: Point,
fill: Fill<T>,
font: Font,
font_size: f32,
features: &[Feature],
text: &str,
) where
T: ColorSpace,
{
let glyphs = naive_shape(text, font.clone(), features, font_size);

self.fill_glyphs(start, fill, &glyphs, font, text);
}

pub fn stroke_text<T>(
&mut self,
start: Point,
stroke: Stroke<T>,
font: Font,
font_size: f32,
features: &[Feature],
text: &str,
) where
T: ColorSpace,
{
let glyphs = naive_shape(text, font.clone(), features, font_size);

self.stroke_glyphs(start, stroke, &glyphs, font, text);
}

pub fn stroke_glyphs<T>(
&mut self,
start: Point,
Expand Down Expand Up @@ -219,6 +253,79 @@ impl Drop for Surface<'_> {
}
}

fn naive_shape(text: &str, font: Font, features: &[Feature], size: f32) -> Vec<Glyph> {
let data = font.font_data();
let rb_font = rustybuzz::Face::from_slice(data.as_ref().as_ref(), font.index()).unwrap();

let mut buffer = UnicodeBuffer::new();
buffer.push_str(text);
buffer.guess_segment_properties();

let dir = buffer.direction();

let output = rustybuzz::shape(&rb_font, features, buffer);

let positions = output.glyph_positions();
let infos = output.glyph_infos();

let mut glyphs = vec![];

for i in 0..output.len() {
let pos = positions[i];
let start_info = infos[i];

let start = start_info.cluster as usize;

let end = if dir == Direction::LeftToRight {
let mut e = i.checked_add(1);
loop {
if let Some(index) = e {
if let Some(end_info) = infos.get(index) {
if end_info.cluster == start_info.cluster {
e = index.checked_add(1);
continue;
}
}
}

break;
}

e
} else {
let mut e = i.checked_sub(1);
loop {
if let Some(index) = e {
if let Some(end_info) = infos.get(index) {
if end_info.cluster == start_info.cluster {
e = index.checked_sub(1);
} else {
break;
}
}
} else {
break;
}
}

e
}
.and_then(|last| infos.get(last))
.map_or(text.len(), |info| info.cluster as usize);

glyphs.push(Glyph::new(
GlyphId::new(start_info.glyph_id),
(pos.x_advance as f32 / font.units_per_em()) * size,
(pos.x_offset as f32 / font.units_per_em()) * size,
(pos.y_offset as f32 / font.units_per_em()) * size,
start..end,
size,
));
}

glyphs
}

pub struct PageBuilder<'a> {
sc: &'a mut SerializerContext,
size: Size,
Expand Down
27 changes: 8 additions & 19 deletions src/tests/manual.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ use crate::rgb::Rgb;
use crate::serialize::SerializeSettings;
use crate::stream::Glyph;
use crate::tests::{
simple_shape, write_manual_to_store, ASSETS_PATH, COLR_TEST_GLYPHS, DEJAVU_SANS_MONO,
NOTO_SANS, NOTO_SANS_ARABIC, NOTO_SANS_CJK, NOTO_SANS_DEVANAGARI,
write_manual_to_store, ASSETS_PATH, COLR_TEST_GLYPHS, DEJAVU_SANS_MONO, NOTO_SANS,
NOTO_SANS_ARABIC, NOTO_SANS_CJK, NOTO_SANS_DEVANAGARI,
};
use crate::util::SliceExt;
use crate::Fill;
use rustybuzz::Direction;
use skrifa::instance::{Location, LocationRef, Size};
use skrifa::raw::TableProvider;
use skrifa::{GlyphId, MetadataProvider};
Expand All @@ -28,38 +27,28 @@ fn simple_shape_demo() {
(
NOTO_SANS_ARABIC.clone(),
"هذا نص أطول لتجربة القدرات.",
Direction::RightToLeft,
14.0,
),
(
NOTO_SANS.clone(),
"Hi there, this is a very simple test!",
Direction::LeftToRight,
14.0,
),
(
DEJAVU_SANS_MONO.clone(),
"Here with a mono font, some longer text.",
Direction::LeftToRight,
16.0,
),
(NOTO_SANS.clone(), "z͈̤̭͖̉͑́a̳ͫ́̇͑̽͒ͯlͨ͗̍̀̍̔̀ģ͔̫̫̄o̗̠͔̦̳͆̏̓͢", Direction::LeftToRight, 14.0),
(
NOTO_SANS.clone(),
" birth\u{ad}day ",
Direction::LeftToRight,
14.0,
),
(NOTO_SANS.clone(), "z͈̤̭͖̉͑́a̳ͫ́̇͑̽͒ͯlͨ͗̍̀̍̔̀ģ͔̫̫̄o̗̠͔̦̳͆̏̓͢", 14.0),
(NOTO_SANS.clone(), " birth\u{ad}day ", 14.0),
(
NOTO_SANS_CJK.clone(),
"你好世界,这是一段很长的测试文章",
Direction::LeftToRight,
14.0,
),
(
NOTO_SANS_DEVANAGARI.clone(),
"आ रु॒क्मैरा यु॒धा नर॑ ऋ॒ष्वा ऋ॒ष्टीर॑सृक्षत ।",
Direction::LeftToRight,
14.0,
),
];
Expand All @@ -68,14 +57,14 @@ fn simple_shape_demo() {
let mut builder = document_builder.start_page(page_size);
let mut surface = builder.surface();

for (font, text, dir, size) in data {
for (font, text, size) in data {
let font = Font::new(font.clone(), 0, Location::default()).unwrap();
let glyphs = simple_shape(text, dir, font.clone(), size);
surface.fill_glyphs(
surface.fill_text(
Point::from_xy(0.0, y),
Fill::<Rgb>::default(),
&glyphs,
font,
size,
&[],
text,
);

Expand Down
75 changes: 0 additions & 75 deletions src/tests/mod.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
use crate::font::Font;
use crate::stream::Glyph;
use difference::{Changeset, Difference};
use image::{load_from_memory, Rgba, RgbaImage};
use oxipng::{InFile, OutFile};
use rustybuzz::{Direction, UnicodeBuffer};
use sitro::{
render_ghostscript, render_mupdf, render_pdfbox, render_pdfium, render_pdfjs, render_poppler,
render_quartz, RenderOptions, RenderedDocument, RenderedPage, Renderer,
};
use skrifa::GlyphId;
use std::cmp::max;
use std::env;
use std::path::PathBuf;
Expand Down Expand Up @@ -238,77 +234,6 @@ pub fn check_render(
}
}

pub fn simple_shape(text: &str, dir: Direction, font: Font, size: f32) -> Vec<Glyph> {
let data = font.font_data();
let rb_font = rustybuzz::Face::from_slice(data.as_ref().as_ref(), 0).unwrap();

let mut buffer = UnicodeBuffer::new();
buffer.push_str(text);
buffer.set_direction(dir);

let output = rustybuzz::shape(&rb_font, &[], buffer);

let positions = output.glyph_positions();
let infos = output.glyph_infos();

let mut glyphs = vec![];

for i in 0..output.len() {
let pos = positions[i];
let start_info = infos[i];

let start = start_info.cluster as usize;

let end = if dir == Direction::LeftToRight {
let mut e = i.checked_add(1);
loop {
if let Some(index) = e {
if let Some(end_info) = infos.get(index) {
if end_info.cluster == start_info.cluster {
e = index.checked_add(1);
continue;
}
}
}

break;
}

e
} else {
let mut e = i.checked_sub(1);
loop {
if let Some(index) = e {
if let Some(end_info) = infos.get(index) {
if end_info.cluster == start_info.cluster {
e = index.checked_sub(1);
} else {
break;
}
}
} else {
break;
}
}

e
}
.and_then(|last| infos.get(last))
.map_or(text.len(), |info| info.cluster as usize);

glyphs.push(Glyph::new(
GlyphId::new(start_info.glyph_id),
(pos.x_advance as f32 / font.units_per_em() as f32) * size,
(pos.x_offset as f32 / font.units_per_em() as f32) * size,
(pos.y_offset as f32 / font.units_per_em() as f32) * size,
start..end,
size,
));
}

glyphs
}

pub fn render_document(doc: &[u8], renderer: &Renderer) -> RenderedDocument {
let options = RenderOptions { scale: 1.0 };

Expand Down

0 comments on commit b2eccf5

Please sign in to comment.