Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Application size? #31

Open
Resonanz opened this issue Jan 21, 2025 · 6 comments
Open

Application size? #31

Resonanz opened this issue Jan 21, 2025 · 6 comments

Comments

@Resonanz
Copy link

How does application size change with usage of this crate?

For example, if I add a single icon, are ALL icons included in the binary, or does the compiler (or crate code) exclude all others?

Perhaps you could add the answer into the Description.

@amPerl
Copy link
Owner

amPerl commented Jan 21, 2025

It'll include all of them. This is not ideal and I'm working on solutions. The easiest one would be to simply use a font subsetter, but there doesn't seem to be a good Rust option for that and I don't want to include python/node tooling in a rust crate. I'm experimenting with both manually subsetting the fonts and also generating functions that could paint the icons with an egui painter at a given rect.

@Resonanz
Copy link
Author

Interesting. I see that they offer a full zip download that includes the icons as SVGs and fonts. I guess you are using Python to download the zip and extract the TTF files.

Curious... can this Python code not be easily re-written in Rust? I see others using Python for helpers, but not sure why.

Just thinking how I would tackle the problem. I would definitely be Rust-only (my strong bias, but makes things easier in one dimension at least if there is only one language). I think I would write some Rust to scan the Rust source and determine which icons are included... then create a new TTF file with just those required icons. Ok, it sounds easy :-) I see that the entire TTF file is about 500 kb so I guess that's not so bad, still it seems silly to include what isn't being used.

@amPerl
Copy link
Owner

amPerl commented Jan 21, 2025

It can. I don't really mind which language is used for generating code if the code ends up committed to the repo and will never have to be run by a user of this crate, which is currently the case.

Source scanning is unreliable when macros are involved. I want to end up with a build.rs macro where you can specify which icons to include. If a user of the crate wants to then scan their own code to generate the line of code that calls that, they may.

Generating TTFs / subsetting is the part I have not found a good solution for. There are loads of parsing crates, but not many that allow you to manipulate the fonts. There's at least one which is a work in progress, but last time I checked it did not support subsetting yet.

@Resonanz
Copy link
Author

This is what ChatGPT suggests LOL.

Converting an SVG image to a TTF font in Rust involves several steps, as Rust does not have a direct library to handle the entire process. You can break the task into manageable parts using libraries for each step:

  1. Parse the SVG File: Use an SVG parsing library like usvg or resvg to parse and process the SVG file.
  2. Convert Paths to Glyphs: Extract the paths from the SVG and convert them into vector data that can represent glyphs.
  3. Create a TTF Font: Use a font creation library like fontdue or ttf-parser (or a lower-level library like ttfgen) to generate a TTF font file.

Here's an outline of the steps and code to achieve this:


1. Parsing the SVG File

You can use the usvg crate to parse the SVG and access its paths.

Add Dependencies

Add the following to your Cargo.toml:

[dependencies]
usvg = "0.31"
ttfgen = "0.3"

2. Extract Paths and Create a Font

Here’s an example workflow for converting an SVG into a TTF font:

Code Example

use usvg::{NodeExt, Options, Tree};
use ttfgen::{Font, Glyph};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Load and parse the SVG file
    let svg_data = std::fs::read_to_string("example.svg")?;
    let options = Options::default();
    let rtree = Tree::from_str(&svg_data, &options.to_ref())?;

    // Create a TTF font
    let mut font = Font::new("CustomFont", "Custom");

    // Iterate through SVG paths and convert them to glyphs
    for (index, node) in rtree.root().descendants().enumerate() {
        if let usvg::NodeKind::Path(ref path) = *node.borrow() {
            let mut glyph = Glyph::new_empty(index as u32);

            // Convert SVG path commands to font glyph points
            for segment in &path.data.segments {
                match segment {
                    usvg::PathSegment::MoveTo { x, y } => {
                        glyph.move_to(*x as f32, *y as f32);
                    }
                    usvg::PathSegment::LineTo { x, y } => {
                        glyph.line_to(*x as f32, *y as f32);
                    }
                    usvg::PathSegment::CurveTo { x1, y1, x2, y2, x, y } => {
                        glyph.curve_to(*x1 as f32, *y1 as f32, *x2 as f32, *y2 as f32, *x as f32, *y as f32);
                    }
                    usvg::PathSegment::ClosePath => {
                        glyph.close_path();
                    }
                }
            }

            // Add the glyph to the font
            font.add_glyph(glyph);
        }
    }

    // Save the font to a TTF file
    let font_data = font.to_bytes()?;
    std::fs::write("output.ttf", font_data)?;

    println!("Font created successfully: output.ttf");
    Ok(())
}

Explanation

  1. SVG Parsing:

    • The usvg crate parses the SVG file and provides access to its paths and shapes.
  2. Convert Paths to Glyphs:

    • Extract the paths (MoveTo, LineTo, CurveTo, ClosePath) and map them to font glyph points using ttfgen.
  3. Create the TTF Font:

    • The ttfgen crate is used to define glyphs and generate a valid TTF font.
  4. Save the Font:

    • The font is saved to a .ttf file using the to_bytes method.

Limitations

  • Complex SVGs: SVGs with gradients, text, or advanced effects will need preprocessing to simplify them into paths.
  • Font Metadata: The above example handles basic font creation. For full metadata (e.g., kerning, hinting), more advanced processing is required.

Advanced Options

If you need a complete solution with hinting and advanced font features, you might need to use external tools like fontforge or a Rust binding for a font library (if available).

Let me know if you'd like further assistance!

@Resonanz
Copy link
Author

What is ttfgen?

ttfgen is a Rust library used for creating and manipulating TrueType fonts (TTF). It provides a relatively simple API for defining glyphs, font properties, and generating valid TTF files. The library is useful when you want to programmatically generate custom fonts, such as embedding vector shapes (e.g., from SVGs) as glyphs into a TTF font file.


Key Features

  1. Create Fonts from Scratch:

    • You can define custom glyphs and save them as a TTF font file.
  2. Define Glyphs Programmatically:

    • Use methods to create outlines for each glyph (e.g., move_to, line_to, curve_to).
  3. Add Metadata:

    • Set font name, family, and other attributes.
  4. Export TTF Files:

    • Generate a valid TTF file from your defined font and glyphs.

Why Use ttfgen?

  1. Programmatic Font Creation: Ideal for dynamically creating fonts for specific use cases (e.g., converting vector graphics into font glyphs).
  2. Lightweight: A simpler alternative compared to fully-featured font libraries like FontForge.
  3. Integration with Rust Projects: Easily integrates into Rust projects without relying on external binaries.

Basic Example

Here's a simple example to create a font using ttfgen:

Add Dependency

Add this to your Cargo.toml:

[dependencies]
ttfgen = "0.3"

Code Example

use ttfgen::{Font, Glyph};

fn main() {
    // Create a new font
    let mut font = Font::new("MyFont", "Regular");

    // Define a simple glyph (e.g., the letter "A")
    let mut glyph = Glyph::new_empty('A' as u32);
    glyph.move_to(0.0, 0.0);
    glyph.line_to(50.0, 100.0);
    glyph.line_to(100.0, 0.0);
    glyph.close_path();
    
    // Add the glyph to the font
    font.add_glyph(glyph);

    // Export the font to a TTF file
    let font_data = font.to_bytes().expect("Failed to generate font");
    std::fs::write("MyFont-Regular.ttf", font_data).expect("Failed to write TTF file");

    println!("Font created: MyFont-Regular.ttf");
}

Explanation

  1. Font::new("MyFont", "Regular"):

    • Creates a new font with the specified family and style name.
  2. Glyph:

    • Represents a single glyph in the font. Use move_to, line_to, and curve_to to define its outline.
  3. to_bytes():

    • Converts the font into a TTF byte array, which can then be written to a file.
  4. Output:

    • The resulting file, MyFont-Regular.ttf, is a valid TrueType font containing the defined glyph.

Limitations of ttfgen

  1. Basic Feature Set:

    • Lacks advanced font features like hinting, kerning tables, and OpenType features.
  2. Simple Glyph Manipulation:

    • Suitable for basic glyphs but not optimized for complex typography.
  3. Requires Manual Glyph Definitions:

    • You need to define the glyph outlines programmatically, which can be tedious for intricate designs.

When to Use ttfgen

  • When creating simple custom fonts: For use in logos, icons, or other specific use cases.
  • When embedding vector shapes into fonts: Especially when automating the process in Rust.

If your use case requires more advanced font features, consider using a more comprehensive tool like FontForge or Glyphs for design and then use Rust to manipulate the final font.

@Resonanz
Copy link
Author

Not sure if any of that is new or helpful. If not, delete :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants