Skip to content

Commit

Permalink
Support the font shorthand and improve font-family parsing.
Browse files Browse the repository at this point in the history
Closes #431
  • Loading branch information
LaurenzV authored Feb 6, 2024
1 parent 22c4bf9 commit 1959a43
Show file tree
Hide file tree
Showing 22 changed files with 160 additions and 80 deletions.
22 changes: 17 additions & 5 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion crates/resvg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ log = "0.4"
pico-args = { version = "0.5", features = ["eq-separator"] }
png = { version = "0.17", optional = true }
rgb = "0.8"
svgtypes = "0.13"
svgtypes = "0.14.0"
tiny-skia = "0.11.4"
usvg = { path = "../usvg", version = "0.38.0", default-features = false }

Expand Down
2 changes: 1 addition & 1 deletion crates/resvg/tests/tests/shapes/rect/ic-values.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
13 changes: 13 additions & 0 deletions crates/resvg/tests/tests/text/font/font-shorthand.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/resvg/tests/tests/text/textPath/complex.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/resvg/tests/tests/text/textPath/writing-mode=tb.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion crates/usvg-parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ log = "0.4"
roxmltree = "0.19"
simplecss = "0.2"
siphasher = "0.3" # perfect hash implementation
svgtypes = "0.13"
svgtypes = "0.14.0"
usvg-tree = { path = "../usvg-tree", version = "0.38.0" }
74 changes: 56 additions & 18 deletions crates/usvg-parser/src/svgtree/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
use std::collections::HashMap;

use roxmltree::Error;
use simplecss::Declaration;
use svgtypes::FontShorthand;

use super::{AId, Attribute, Document, EId, NodeData, NodeId, NodeKind, ShortRange};

Expand Down Expand Up @@ -266,35 +268,71 @@ pub(crate) fn parse_svg_element<'input>(
}
};

let mut write_declaration = |declaration: &Declaration| {
// TODO: perform XML attribute normalization
if declaration.name == "marker" {
insert_attribute(AId::MarkerStart, declaration.value);
insert_attribute(AId::MarkerMid, declaration.value);
insert_attribute(AId::MarkerEnd, declaration.value);
} else if declaration.name == "font" {
if let Ok(shorthand) = FontShorthand::from_str(declaration.value) {
// First we need to reset all values to their default.
insert_attribute(AId::FontStyle, "normal");
insert_attribute(AId::FontVariant, "normal");
insert_attribute(AId::FontWeight, "normal");
insert_attribute(AId::FontStretch, "normal");
insert_attribute(AId::LineHeight, "normal");
insert_attribute(AId::FontSizeAdjust, "none");
insert_attribute(AId::FontKerning, "auto");
insert_attribute(AId::FontVariantCaps, "normal");
insert_attribute(AId::FontVariantLigatures, "normal");
insert_attribute(AId::FontVariantNumeric, "normal");
insert_attribute(AId::FontVariantEastAsian, "normal");
insert_attribute(AId::FontVariantPosition, "normal");

// Then, we set the properties that have been declared.
shorthand
.font_stretch
.map(|s| insert_attribute(AId::FontStretch, s));
shorthand
.font_weight
.map(|s| insert_attribute(AId::FontWeight, s));
shorthand
.font_variant
.map(|s| insert_attribute(AId::FontVariant, s));
shorthand
.font_style
.map(|s| insert_attribute(AId::FontStyle, s));
insert_attribute(AId::FontSize, shorthand.font_size);
insert_attribute(AId::FontFamily, shorthand.font_family);
} else {
log::warn!(
"Failed to parse {} value: '{}'",
AId::Font,
declaration.value
);
}
} else if let Some(aid) = AId::from_str(declaration.name) {
// Parse only the presentation attributes.
if aid.is_presentation() {
insert_attribute(aid, declaration.value);
}
}
};

// Apply CSS.
for rule in &style_sheet.rules {
if rule.selector.matches(&XmlNode(xml_node)) {
for declaration in &rule.declarations {
// TODO: perform XML attribute normalization
if let Some(aid) = AId::from_str(declaration.name) {
// Parse only the presentation attributes.
if aid.is_presentation() {
insert_attribute(aid, declaration.value);
}
} else if declaration.name == "marker" {
insert_attribute(AId::MarkerStart, declaration.value);
insert_attribute(AId::MarkerMid, declaration.value);
insert_attribute(AId::MarkerEnd, declaration.value);
}
write_declaration(declaration);
}
}
}

// Split a `style` attribute.
if let Some(value) = xml_node.attribute("style") {
for declaration in simplecss::DeclarationTokenizer::from(value) {
// TODO: preform XML attribute normalization
if let Some(aid) = AId::from_str(declaration.name) {
// Parse only the presentation attributes.
if aid.is_presentation() {
insert_attribute(aid, declaration.value);
}
}
write_declaration(&declaration);
}
}

Expand Down
38 changes: 16 additions & 22 deletions crates/usvg-parser/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
use std::rc::Rc;

use kurbo::{ParamCurve, ParamCurveArclen};
use svgtypes::{Length, LengthUnit};
use svgtypes::{parse_font_families, FontFamily, Length, LengthUnit};
use usvg_tree::*;

use crate::svgtree::{AId, EId, FromValue, SvgNode};
use crate::{converter, style};
use crate::{converter, style, OptionLog};

impl<'a, 'input: 'a> FromValue<'a, 'input> for usvg_tree::TextAnchor {
fn parse(_: SvgNode, _: AId, value: &str) -> Option<Self> {
Expand Down Expand Up @@ -383,33 +383,27 @@ fn convert_font(node: SvgNode, state: &converter::State) -> Font {
let stretch = conv_font_stretch(node);
let weight = resolve_font_weight(node);

let font_family = if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontFamily)) {
let font_families = if let Some(n) = node.ancestors().find(|n| n.has_attribute(AId::FontFamily))
{
n.attribute(AId::FontFamily).unwrap_or("")
} else {
""
};

let mut families = Vec::new();
for mut family in font_family.split(',') {
// TODO: to a proper parser

family = family.trim();

if family.starts_with(['\'', '"']) {
family = &family[1..];
}

if family.ends_with(['\'', '"']) {
family = &family[..family.len() - 1];
}

if !family.is_empty() {
families.push(family.to_string());
}
}
let mut families = parse_font_families(font_families)
.ok()
.log_none(|| {
log::warn!(
"Failed to parse {} value: '{}'. Falling back to {}.",
AId::FontFamily,
font_families,
state.opt.font_family
)
})
.unwrap_or_default();

if families.is_empty() {
families.push(state.opt.font_family.clone())
families.push(FontFamily::Named(state.opt.font_family.clone()))
}

Font {
Expand Down
1 change: 1 addition & 0 deletions crates/usvg-text-layout/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ unicode-bidi = "0.3"
unicode-script = "0.5"
unicode-vo = "0.1"
usvg-tree = { path = "../usvg-tree", version = "0.38.0" }
svgtypes = "0.14.0"

[features]
default = ["system-fonts", "memmap-fonts"]
Expand Down
24 changes: 16 additions & 8 deletions crates/usvg-text-layout/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use std::rc::Rc;
use fontdb::{Database, ID};
use kurbo::{ParamCurve, ParamCurveArclen, ParamCurveDeriv};
use rustybuzz::ttf_parser;
use svgtypes::FontFamily;
use ttf_parser::GlyphId;
use unicode_script::UnicodeScript;
use usvg_tree::*;
Expand Down Expand Up @@ -582,13 +583,13 @@ fn text_to_paths(
fn resolve_font(font: &Font, fontdb: &fontdb::Database) -> Option<ResolvedFont> {
let mut name_list = Vec::new();
for family in &font.families {
name_list.push(match family.as_str() {
"serif" => fontdb::Family::Serif,
"sans-serif" => fontdb::Family::SansSerif,
"cursive" => fontdb::Family::Cursive,
"fantasy" => fontdb::Family::Fantasy,
"monospace" => fontdb::Family::Monospace,
_ => fontdb::Family::Name(family),
name_list.push(match family {
FontFamily::Serif => fontdb::Family::Serif,
FontFamily::SansSerif => fontdb::Family::SansSerif,
FontFamily::Cursive => fontdb::Family::Cursive,
FontFamily::Fantasy => fontdb::Family::Fantasy,
FontFamily::Monospace => fontdb::Family::Monospace,
FontFamily::Named(s) => fontdb::Family::Name(s),
});
}

Expand Down Expand Up @@ -622,7 +623,14 @@ fn resolve_font(font: &Font, fontdb: &fontdb::Database) -> Option<ResolvedFont>

let id = fontdb.query(&query);
if id.is_none() {
log::warn!("No match for '{}' font-family.", font.families.join(", "));
log::warn!(
"No match for '{}' font-family.",
font.families
.iter()
.map(|f| f.to_string())
.collect::<Vec<_>>()
.join(", ")
);
}

fontdb.load_font(id?)
Expand Down
2 changes: 1 addition & 1 deletion crates/usvg-tree/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ workspace = "../.."

[dependencies]
strict-num = "0.1.1"
svgtypes = "0.13"
svgtypes = "0.14.0"
tiny-skia-path = "0.11.4"
3 changes: 2 additions & 1 deletion crates/usvg-tree/src/text.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
use std::rc::Rc;

use strict_num::NonZeroPositiveF32;
pub use svgtypes::FontFamily;

use crate::{Fill, Group, Paint, PaintOrder, Stroke, TextRendering, Visibility};
use tiny_skia_path::{NonZeroRect, Transform};
Expand Down Expand Up @@ -55,7 +56,7 @@ pub struct Font {
/// A list of family names.
///
/// Never empty. Uses `usvg_parser::Options::font_family` as fallback.
pub families: Vec<String>,
pub families: Vec<FontFamily>,
/// A font style.
pub style: FontStyle,
/// A font stretch.
Expand Down
1 change: 1 addition & 0 deletions crates/usvg/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ pico-args = { version = "0.5", features = ["eq-separator"] }
usvg-parser = { path = "../usvg-parser", version = "0.38.0" }
usvg-tree = { path = "../usvg-tree", version = "0.38.0" }
xmlwriter = "0.1"
svgtypes = "0.14.0"

[dependencies.usvg-text-layout]
path = "../usvg-text-layout"
Expand Down
Loading

0 comments on commit 1959a43

Please sign in to comment.