Skip to content
Merged
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
Binary file added __test__/Pacifico-Regular.ttf
Binary file not shown.
Binary file added __test__/options_font_buffer_expected_result.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
23 changes: 23 additions & 0 deletions __test__/wasm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -328,6 +328,29 @@ test('should return undefined if bbox is invalid', (t) => {
t.is(resvg.innerBBox(), undefined)
})

test('should render using font buffer provided by options', async (t) => {
const svg = `<svg width='480' height='150' viewBox='-20 -80 550 100' xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink'>
<text x='0' y='0' font-size='100' font-family='Pacifico' fill='#000000'>Font Buffer</text>
</svg>`

const pacificoBuffer = await fs.readFile(join(__dirname, './Pacifico-Regular.ttf'))
const expectedResultBuffer = await fs.readFile(join(__dirname, './options_font_buffer_expected_result.png'))

const options = {
font: {
fontsBuffers: [pacificoBuffer],
},
}

const resvg = new Resvg(svg, options)
const renderedResult = resvg.render().asPng()

const expectedResult = await jimp.read(Buffer.from(expectedResultBuffer.buffer))
const actualPng = await jimp.read(Buffer.from(renderedResult))

t.is(jimp.diff(expectedResult, actualPng, 0.01).percent, 0) // 0 means similar, 1 means not similar
})

// throws
test('should throw because invalid SVG (blank string)', (t) => {
const error = t.throws(
Expand Down
74 changes: 51 additions & 23 deletions src/fonts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#[cfg(not(target_arch = "wasm32"))]
use crate::options::*;
use resvg::usvg_text_layout::fontdb::Database;

#[cfg(not(target_arch = "wasm32"))]
use log::{debug, warn};

#[cfg(not(target_arch = "wasm32"))]
use resvg::usvg_text_layout::fontdb::{Database, Family, Query, Source};
use resvg::usvg_text_layout::fontdb::{Family, Query, Source};

#[cfg(target_arch = "wasm32")]
use wasm_bindgen::JsCast;

/// Loads fonts.
#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -36,27 +39,8 @@ pub fn load_fonts(font_options: &JsFontOptions) -> Database {
fontdb.load_fonts_dir(path);
}

// Set generic font families
// - `serif` - Times New Roman
// - `sans-serif` - Arial
// - `cursive` - Comic Sans MS
// - `fantasy` - Impact (Papyrus on macOS)
// - `monospace` - Courier New
if !font_options.default_font_family.is_empty() {
// If a default font family exists, set all other families to that family.
// This prevents fonts from not being rendered in SVG.
fontdb.set_serif_family(&font_options.default_font_family);
fontdb.set_sans_serif_family(&font_options.default_font_family);
fontdb.set_cursive_family(&font_options.default_font_family);
fontdb.set_fantasy_family(&font_options.default_font_family);
fontdb.set_monospace_family(&font_options.default_font_family);
} else {
fontdb.set_serif_family(&font_options.serif_family);
fontdb.set_sans_serif_family(&font_options.sans_serif_family);
fontdb.set_cursive_family(&font_options.cursive_family);
fontdb.set_fantasy_family(&font_options.fantasy_family);
fontdb.set_monospace_family(&font_options.monospace_family);
}
set_font_families(font_options, &mut fontdb);

debug!(
"Loaded {} font faces in {}ms.",
fontdb.len(),
Expand Down Expand Up @@ -91,3 +75,47 @@ pub fn load_fonts(font_options: &JsFontOptions) -> Database {

fontdb
}

/// Loads fonts.
#[cfg(target_arch = "wasm32")]
pub fn load_fonts(
font_options: &JsFontOptions,
fonts_buffers: Option<js_sys::Array>,
fontdb: &mut Database,
) -> Result<(), js_sys::Error> {
if let Some(fonts_buffers) = fonts_buffers {
for font in fonts_buffers.values().into_iter() {
let raw_font = font?;
let font_data = raw_font.dyn_into::<js_sys::Uint8Array>()?.to_vec();
fontdb.load_font_data(font_data);
}
}

set_font_families(font_options, fontdb);

Ok(())
}

fn set_font_families(font_options: &JsFontOptions, fontdb: &mut Database) {
// Set generic font families
// - `serif` - Times New Roman
// - `sans-serif` - Arial
// - `cursive` - Comic Sans MS
// - `fantasy` - Impact (Papyrus on macOS)
// - `monospace` - Courier New
if !font_options.default_font_family.is_empty() {
// If a default font family exists, set all other families to that family.
// This prevents fonts from not being rendered in SVG.
fontdb.set_serif_family(&font_options.default_font_family);
fontdb.set_sans_serif_family(&font_options.default_font_family);
fontdb.set_cursive_family(&font_options.default_font_family);
fontdb.set_fantasy_family(&font_options.default_font_family);
fontdb.set_monospace_family(&font_options.default_font_family);
} else {
fontdb.set_serif_family(&font_options.serif_family);
fontdb.set_sans_serif_family(&font_options.sans_serif_family);
fontdb.set_cursive_family(&font_options.cursive_family);
fontdb.set_fantasy_family(&font_options.fantasy_family);
fontdb.set_monospace_family(&font_options.monospace_family);
}
}
23 changes: 16 additions & 7 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,11 @@ use pathfinder_content::{
};
use pathfinder_geometry::rect::RectF;
use pathfinder_geometry::vector::Vector2F;
use resvg::tiny_skia::Pixmap;
use resvg::usvg::{self, ImageKind, NodeKind};
#[cfg(not(target_arch = "wasm32"))]
use resvg::usvg_text_layout::TreeTextToPath;
use resvg::{
tiny_skia::Pixmap,
usvg::{self, ImageKind, NodeKind},
usvg_text_layout::TreeTextToPath,
};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::{
prelude::{wasm_bindgen, JsValue},
Expand Down Expand Up @@ -276,14 +277,21 @@ impl Resvg {
#[wasm_bindgen]
impl Resvg {
#[wasm_bindgen(constructor)]
pub fn new(svg: IStringOrBuffer, options: Option<String>) -> Result<Resvg, js_sys::Error> {
pub fn new(
svg: IStringOrBuffer,
options: Option<String>,
custom_font_buffers: Option<js_sys::Array>,
) -> Result<Resvg, js_sys::Error> {
let js_options: JsOptions = options
.and_then(|o| serde_json::from_str(o.as_str()).ok())
.unwrap_or_default();

let (mut opts, _) = js_options.to_usvg_options();
let (mut opts, mut fontdb) = js_options.to_usvg_options();

crate::fonts::load_fonts(&js_options.font, custom_font_buffers, &mut fontdb)?;

options::tweak_usvg_options(&mut opts);
let tree = if js_sys::Uint8Array::instanceof(&svg) {
let mut tree = if js_sys::Uint8Array::instanceof(&svg) {
let uintarray = js_sys::Uint8Array::unchecked_from_js_ref(&svg);
let svg_buffer = uintarray.to_vec();
usvg::Tree::from_data(&svg_buffer, &opts).map_err(Error::from)
Expand All @@ -292,6 +300,7 @@ impl Resvg {
} else {
Err(Error::InvalidInput)
}?;
tree.convert_text(&fontdb);
Ok(Resvg { tree, js_options })
}

Expand Down
24 changes: 21 additions & 3 deletions wasm-binding.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import init, { Resvg as _Resvg, InitInput } from './wasm/dist'

import { ResvgRenderOptions } from './index'
import { CustomFontsOptions, ResvgRenderOptions, SystemFontsOptions } from './wasm/index'

let initialized = false

Expand All @@ -24,6 +23,25 @@ export const Resvg = class extends _Resvg {
*/
constructor(svg: Uint8Array | string, options?: ResvgRenderOptions) {
if (!initialized) throw new Error('Wasm has not been initialized. Call `initWasm()` function.')
super(svg, JSON.stringify(options))

const font = options?.font

if (!!font && isCustomFontsOptions(font)) {
const serializableOptions = {
...options,
font: {
...font,
fontsBuffers: undefined,
},
}

super(svg, JSON.stringify(serializableOptions), font.fontsBuffers)
} else {
super(svg, JSON.stringify(options))
}
}
}

function isCustomFontsOptions(value: SystemFontsOptions | CustomFontsOptions): value is CustomFontsOptions {
return Object.prototype.hasOwnProperty.call(value, 'fontsBuffers')
}
18 changes: 18 additions & 0 deletions wasm/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ declare class RenderedImage {
}
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export type ResvgRenderOptions = {
font?: SystemFontsOptions | CustomFontsOptions;
dpi?: number;
languages?: string[];
shapeRendering?: 0 // optimizeSpeed
Expand Down Expand Up @@ -67,6 +68,23 @@ export type ResvgRenderOptions = {
bottom?: number;
};
};
export type FontOptions = {
defaultFontSize?: number; // Default: 12
defaultFontFamily?: string;
serifFamily?: string;
sansSerifFamily?: string;
cursiveFamily?: string;
fantasyFamily?: string;
monospaceFamily?: string;
};
export type CustomFontsOptions = {
fontsBuffers: Uint8Array[]; // A list of raw font files to load.
} & FontOptions;
export type SystemFontsOptions = {
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.
} & FontOptions;
/**
* Initialize Wasm module
* @param module_or_path WebAssembly Module or .wasm url
Expand Down
47 changes: 44 additions & 3 deletions wasm/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,13 @@ function _assertClass(instance, klass) {
}
return instance.ptr;
}
function handleError(f, args) {
try {
return f.apply(this, args);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
}
var BBox = class {
static __wrap(ptr) {
const obj = Object.create(BBox.prototype);
Expand Down Expand Up @@ -267,13 +274,14 @@ var Resvg = class {
/**
* @param {Uint8Array | string} svg
* @param {string | undefined} options
* @param {Array<any> | undefined} custom_font_buffers
*/
constructor(svg, options) {
constructor(svg, options, custom_font_buffers) {
try {
const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
var ptr0 = isLikeNone(options) ? 0 : passStringToWasm0(options, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
wasm.resvg_new(retptr, addHeapObject(svg), ptr0, len0);
wasm.resvg_new(retptr, addHeapObject(svg), ptr0, len0, isLikeNone(custom_font_buffers) ? 0 : addHeapObject(custom_font_buffers));
var r0 = getInt32Memory0()[retptr / 4 + 0];
var r1 = getInt32Memory0()[retptr / 4 + 1];
var r2 = getInt32Memory0()[retptr / 4 + 2];
Expand Down Expand Up @@ -453,6 +461,24 @@ function getImports() {
const ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_values_97683218f24ed826 = function(arg0) {
const ret = getObject(arg0).values();
return addHeapObject(ret);
};
imports.wbg.__wbg_next_88560ec06a094dea = function() {
return handleError(function(arg0) {
const ret = getObject(arg0).next();
return addHeapObject(ret);
}, arguments);
};
imports.wbg.__wbg_done_1ebec03bbd919843 = function(arg0) {
const ret = getObject(arg0).done;
return ret;
};
imports.wbg.__wbg_value_6ac8da5cc5b3efda = function(arg0) {
const ret = getObject(arg0).value;
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Uint8Array_01cebe79ca606cca = function(arg0) {
let result;
try {
Expand Down Expand Up @@ -535,6 +561,21 @@ var Resvg2 = class extends Resvg {
constructor(svg, options) {
if (!initialized)
throw new Error("Wasm has not been initialized. Call `initWasm()` function.");
super(svg, JSON.stringify(options));
const font = options == null ? void 0 : options.font;
if (!!font && isCustomFontsOptions(font)) {
const serializableOptions = {
...options,
font: {
...font,
fontsBuffers: void 0
}
};
super(svg, JSON.stringify(serializableOptions), font.fontsBuffers);
} else {
super(svg, JSON.stringify(options));
}
}
};
function isCustomFontsOptions(value) {
return Object.prototype.hasOwnProperty.call(value, "fontsBuffers");
}
Loading