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

preload font files into memory to avoid frequent IO. #366

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 37 additions & 1 deletion __test__/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import test from 'ava'
import jimp from 'jimp-compact'
import fetch from 'node-fetch'

import { Resvg, renderAsync } from '../index'
import { Resvg, ResvgRenderOptions, renderAsync } from '../index'

import { jimpToRgbaPixels } from './helper'

Expand Down Expand Up @@ -704,3 +704,39 @@ test('should throw (SVG string is empty)', (t) => {
t.is(error.code, 'GenericFailure')
t.is(error.message, 'SVG data parsing failed cause the document does not have a root node')
})

// issue: https://github.com/thx/resvg-js/issues/367
test('Load custom font with preload_fonts', async (t) => {
const filePath = '../example/text.svg'
const svg = await fs.readFile(join(__dirname, filePath))
const svgString = svg.toString('utf-8')
const options: ResvgRenderOptions = {
font: {
fontFiles: ['./example/SourceHanSerifCN-Light-subset.ttf'], // Load custom fonts.
loadSystemFonts: false, // It will be faster to disable loading system fonts.
defaultFontFamily: 'Source Han Serif CN Light',
preloadFonts: false,
},
}
const expectResvg = new Resvg(svgString, options)
const expectPngData = expectResvg.render()
const expectPngBuffer = expectPngData.asPng()
const expectedResult = await jimp.read(Buffer.from(expectPngBuffer))

// enable preloadFonts
options.font!.preloadFonts = true
const actualResvg = new Resvg(svgString, options)
const actualPngData = actualResvg.render()
const actualPngBuffer = actualPngData.asPng()
const actualPng = await jimp.read(Buffer.from(actualPngBuffer))
t.is(jimp.diff(expectedResult, actualPng, 0.01).percent, 0) // 0 means similar, 1 means not similar

// use fontDirs
delete options.font!.fontFiles
options.font!.fontDirs = ['./example']
const actualResvg2 = new Resvg(svgString, options)
const actualPngData2 = actualResvg2.render()
const actualPngBuffer2 = actualPngData2.asPng()
const actualPng2 = await jimp.read(Buffer.from(actualPngBuffer2))
t.is(jimp.diff(expectedResult, actualPng2, 0.01).percent, 0) // 0 means similar, 1 means not similar
})
1 change: 1 addition & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export type ResvgRenderOptions = {
loadSystemFonts?: boolean // Default: true, if set to false, it will be faster.
fontFiles?: string[] // A list of local font file paths to load.
fontDirs?: string[] // A list of local font directories to load.
preloadFonts?: boolean // Default: false. When enabled, font files will be read into memory at once to avoid frequent IO.
defaultFontSize?: number // Default: 12
defaultFontFamily?: string // Default: "", if `loadSystemFonts` is enabled, it will be set to the first font in the list of system fonts.
serifFamily?: string
Expand Down
57 changes: 48 additions & 9 deletions src/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,62 @@ use wasm_bindgen::JsCast;
#[cfg(target_arch = "wasm32")]
use woff2::decode::{convert_woff2_to_ttf, is_woff2};

#[cfg(not(target_arch = "wasm32"))]
fn load_font_buffer(path: impl AsRef<std::path::Path>) -> Option<Vec<u8>> {
match std::fs::read(path.as_ref()) {
Ok(buffer) => Some(buffer),
Err(e) => {
warn!(
"Failed to read font file '{}' cause {}.",
path.as_ref().display(),
e
);
None
}
}
}

/// Loads fonts.
#[cfg(not(target_arch = "wasm32"))]
pub fn load_fonts(font_options: &JsFontOptions) -> Database {
// Create a new font database
let mut fontdb = Database::new();
let now = std::time::Instant::now();

// 加载指定路径的字体
for path in &font_options.font_files {
if let Err(e) = fontdb.load_font_file(path) {
warn!("Failed to load '{}' cause {}.", path, e);
if font_options.preload_fonts {
// 预加载单个字体文件
font_options
.font_files
.iter()
.filter_map(load_font_buffer)
.for_each(|buffer| {
let _ = fontdb.load_font_data(buffer);
});

// 预加载字体目录
for dir in &font_options.font_dirs {
if let Ok(entries) = std::fs::read_dir(dir) {
entries
.filter_map(Result::ok)
.filter_map(|entry| entry.path().canonicalize().ok())
.filter(|path| path.is_file())
.filter_map(load_font_buffer)
.for_each(|buffer| {
let _ = fontdb.load_font_data(buffer);
});
}
}
} else {
// 默认模式: 直接传递文件路径
for path in &font_options.font_files {
if let Err(e) = fontdb.load_font_file(path) {
warn!("Failed to load '{}' cause {}.", path, e);
}
}
}

// Load font directories
for path in &font_options.font_dirs {
fontdb.load_fonts_dir(path);
// Load font directories
for path in &font_options.font_dirs {
fontdb.load_fonts_dir(path);
}
}

// 加载系统字体
Expand Down
7 changes: 7 additions & 0 deletions src/options.rs
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,12 @@ pub struct JsFontOptions {
/// A list of local font directories to load.
pub font_dirs: Vec<String>,

/// Whether to preload font files into memory to improve performance.
/// When enabled, font files will be read into memory at once to avoid frequent IO.
///
/// Default: false
pub preload_fonts: bool,

/// The default font family.
///
/// Will be used when no `font-family` attribute is set in the SVG.
Expand Down Expand Up @@ -284,6 +290,7 @@ impl Default for JsFontOptions {
load_system_fonts: true,
font_files: vec![],
font_dirs: vec![],
preload_fonts: false,
default_font_family: "".to_string(),
default_font_size: 12.0,
serif_family: "Times New Roman".to_string(),
Expand Down
Loading