Skip to content

Commit

Permalink
Add text feature
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenzV committed Mar 30, 2024
1 parent 266fb9f commit 9165af7
Show file tree
Hide file tree
Showing 11 changed files with 163 additions and 99 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]
- Added support for text embedding.
- Added a `text` feature flag.
- The `convert_str` method has been removed. You should now always convert your SVG string into a `usvg`
tree yourself.
- The `convert_tree` method has been renamed into `to_pdf`, and now requires you to provide the fontdb
Expand All @@ -17,8 +18,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- TODO: The CLI options have been (temporarily) removed. They will be readded before the next release.
- TODO: Add tests for CLI and svg options
- TODO: Add CLI option to convert text to paths.
- TODO: Add CI test to test builds with different feature
- TODO: Add text feature?

### Changed
- Bumped resvg to v0.40.
Expand Down
6 changes: 3 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 12 additions & 8 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,10 @@ oxipng = { version = "9", default-features = false, features = ["filetime", "par
pdf-writer = "0.9"
pdfium-render = "0.8.6"
termcolor = "1.2"
usvg = { git = "https://github.com/RazrFalcon/resvg", default-features = false, features = ["text"] }
usvg = { git = "https://github.com/RazrFalcon/resvg", default-features = false}
tiny-skia = "0.11.4"
unicode-properties = "0.1.1"
resvg = {git = "https://github.com/RazrFalcon/resvg"}
resvg = {git = "https://github.com/RazrFalcon/resvg", default-features = false}
subsetter = "0.1.1"
ttf-parser = { version = "0.20.0" }
siphasher = { version = "1.0.1"}
Expand All @@ -48,21 +48,25 @@ license = { workspace = true }
bench = false

[features]
default = ["image", "filters"]
default = ["image", "filters", "text"]
text = ["usvg/text", "resvg/text", "dep:siphasher",
"dep:subsetter", "dep:ttf-parser", "dep:unicode-properties",
"dep:fontdb"]
image = ["dep:image"]
filters = ["image", "dep:tiny-skia", "dep:resvg"]
filters = ["image", "dep:tiny-skia", "resvg/raster-images"]

[dependencies]
unicode-properties = { workspace = true }
unicode-properties = { workspace = true, optional = true }
miniz_oxide = { workspace = true }
once_cell = { workspace = true }
pdf-writer = { workspace = true }
fontdb = { workspace = true, optional = true}
usvg = { workspace = true }
log = { workspace = true }
image = { workspace = true, optional = true }
tiny-skia = {workspace = true, optional = true }
resvg = {workspace = true, optional = true }
subsetter = { workspace = true }
ttf-parser = { workspace = true }
siphasher = { workspace = true }
subsetter = { workspace = true, optional = true }
ttf-parser = { workspace = true, optional = true }
siphasher = { workspace = true, optional = true }

7 changes: 4 additions & 3 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ doc = false

[dependencies]
clap = { workspace = true }
fontdb = { workspace = true }
image = { workspace = true }
# TODO: Don't include if not build with text feature
fontdb = { workspace = true}
log = { workspace = true }
miniz_oxide = { workspace = true }
pdf-writer = { workspace = true }
Expand All @@ -30,9 +30,10 @@ termcolor = { workspace = true }
usvg = { workspace = true }

[features]
default = ["svg2pdf/default"]
default = ["image", "filters", "text"]
image = ["svg2pdf/image"]
filters = ["svg2pdf/filters"]
text = ["svg2pdf/text", "usvg/text"]

[build-dependencies]
clap = { workspace = true, features = ["string"] }
Expand Down
18 changes: 14 additions & 4 deletions cli/src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,20 @@ pub fn convert_(input: &PathBuf, output: Option<PathBuf>) -> Result<(), String>

let options = usvg::Options::default();

let tree =
usvg::Tree::from_str(&svg, &options, &fontdb).map_err(|err| err.to_string())?;

let pdf = svg2pdf::to_pdf(&tree, Options::default(), &fontdb);
let tree = usvg::Tree::from_str(
&svg,
&options,
#[cfg(feature = "text")]
&fontdb,
)
.map_err(|err| err.to_string())?;

let pdf = svg2pdf::to_pdf(
&tree,
Options::default(),
#[cfg(feature = "text")]
&fontdb,
);

std::fs::write(output, pdf).map_err(|_| "Failed to write PDF file")?;

Expand Down
28 changes: 23 additions & 5 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,9 @@ pub use usvg;

use once_cell::sync::Lazy;
use pdf_writer::{Chunk, Content, Filter, Finish, Pdf, Rect, Ref, TextStr};
use usvg::{fontdb, Tree};
#[cfg(feature = "text")]
use usvg::fontdb;
use usvg::Tree;

use crate::render::{tree_to_stream, tree_to_xobject};
use crate::util::context::Context;
Expand Down Expand Up @@ -127,8 +129,18 @@ impl Default for Options {
/// std::fs::write(output, pdf)?;
/// # Ok(()) }
/// ```
pub fn to_pdf(tree: &Tree, options: Options, fontdb: &fontdb::Database) -> Vec<u8> {
let mut ctx = Context::new(tree, options, fontdb);
pub fn to_pdf(
tree: &Tree,
options: Options,
#[cfg(feature = "text")] fontdb: &fontdb::Database,
) -> Vec<u8> {
let mut ctx = Context::new(
#[cfg(feature = "text")]
tree,
options,
#[cfg(feature = "text")]
fontdb,
);
let mut pdf = Pdf::new();

let catalog_ref = ctx.alloc_ref();
Expand Down Expand Up @@ -275,11 +287,17 @@ pub fn to_pdf(tree: &Tree, options: Options, fontdb: &fontdb::Database) -> Vec<u
pub fn to_chunk(
tree: &Tree,
options: Options,
fontdb: &fontdb::Database,
#[cfg(feature = "text")] fontdb: &fontdb::Database,
) -> (Chunk, Ref) {
let mut chunk = Chunk::new();

let mut ctx = Context::new(tree, options, fontdb);
let mut ctx = Context::new(
#[cfg(feature = "text")]
tree,
options,
#[cfg(feature = "text")]
fontdb,
);
let x_ref = tree_to_xobject(tree, &mut chunk, &mut ctx);
ctx.write_global_objects(&mut chunk);
(chunk, x_ref)
Expand Down
2 changes: 1 addition & 1 deletion src/render/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ pub fn render(

#[cfg(not(feature = "filters"))]
if !group.filters().is_empty() {
log::warn!("Filters have been disabled in this build of svg2pdf.")
log::warn!("Failed convert filter because the filters feature was disabled. Skipping.")
}

let initial_opacity = initial_opacity.unwrap_or(Opacity::ONE);
Expand Down
8 changes: 7 additions & 1 deletion src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub mod image;
pub mod mask;
pub mod path;
pub mod pattern;
#[cfg(feature = "text")]
pub mod text;

/// Write a tree into a stream. Assumes that the stream belongs to transparency group and the object
Expand Down Expand Up @@ -107,8 +108,9 @@ impl Render for Node {
),
#[cfg(not(feature = "image"))]
Node::Image(_) => {
log::warn!("Images have been disabled in this build of svg2pdf.")
log::warn!("Failed convert image because the image feature was disabled. Skipping.")
}
#[cfg(feature = "text")]
Node::Text(ref text) => {
text::render(text, chunk, content, ctx, rc, accumulated_transform);
// group::render(
Expand All @@ -120,6 +122,10 @@ impl Render for Node {
// None,
// );
}
#[cfg(not(feature = "text"))]
Node::Text(_) => {
log::warn!("Failed convert text because the text feature was disabled. Skipping.")
}
}
}
}
66 changes: 63 additions & 3 deletions src/render/text.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
use crate::render::path;
use crate::util::allocate::RefAllocator;
use crate::util::context::{Context, Font};
use crate::util::context::Context;
use crate::util::helper::{deflate, TransformExt};
use crate::util::resources::ResourceContainer;
use pdf_writer::types::{
CidFontType, FontFlags, SystemInfo, TextRenderingMode, UnicodeCmap,
};
use pdf_writer::{Chunk, Content, Filter, Finish, Name, Str};
use pdf_writer::{Chunk, Content, Filter, Finish, Name, Ref, Str};
use siphasher::sip128::{Hasher128, SipHasher13};
use std::collections::{BTreeMap, HashMap};
use std::hash::Hash;
use ttf_parser::{name_id, Face, GlyphId, PlatformId, Tag};
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
use usvg::{Fill, PaintOrder, Stroke, Transform, Visibility};
use usvg::{Fill, Group, ImageKind, Node, PaintOrder, Stroke, Transform, Visibility};

const CFF: Tag = Tag::from_bytes(b"CFF ");
const CFF2: Tag = Tag::from_bytes(b"CFF2");
Expand Down Expand Up @@ -473,3 +473,63 @@ where
Some((key, head))
}
}

#[derive(Clone)]
pub struct Font {
pub glyph_set: BTreeMap<u16, String>,
pub reference: Ref,
pub face_data: Vec<u8>,
pub units_per_em: u16,
pub face_index: u32,
}

pub fn fill_fonts(group: &Group, ctx: &mut Context, fontdb: &fontdb::Database) {
for child in group.children() {
match child {
Node::Text(t) => {
let allocator = &mut ctx.ref_allocator;
for span in t.layouted() {
for g in &span.positioned_glyphs {
let font = ctx.fonts.entry(g.font).or_insert_with(|| {
fontdb
.with_face_data(g.font, |data, face_index| {
// TODO: Currently, we are parsing each font twice, once here
// and once again when writing the fonts. We should probably
// improve on that...
if let Ok(ttf) =
ttf_parser::Face::parse(data, face_index)
{
let reference = allocator.alloc_ref();
let glyph_set = BTreeMap::new();
return Some(Font {
reference,
face_data: Vec::from(data),
units_per_em: ttf.units_per_em(),
glyph_set,
face_index,
});
}

None
})
.flatten()
});

if let Some(ref mut font) = font {
font.glyph_set.insert(g.glyph_id.0, g.text.clone());
}
}
}
}
Node::Group(group) => fill_fonts(group, ctx, fontdb),
Node::Image(image) => {
if let ImageKind::SVG(svg) = image.kind() {
fill_fonts(svg.root(), ctx, fontdb);
}
}
_ => {}
}

child.subroots(|subroot| fill_fonts(subroot, ctx, fontdb));
}
}
Loading

0 comments on commit 9165af7

Please sign in to comment.