Skip to content

Commit 247ea7e

Browse files
committed
feat: add padding parameter to cropByBBox() API to specify the bleed area around the bounding box.
1 parent f1a9e85 commit 247ea7e

File tree

11 files changed

+103
-43
lines changed

11 files changed

+103
-43
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ This changelog also contains important changes in dependencies.
99

1010
## [Unreleased]
1111

12+
- Add `padding` parameter to `cropByBBox()` API to specify the bleed area around the bounding box. Default is `0`. Thanks to @yisibl.
13+
This is similar to the padding property in CSS. If the width parameter is not specified, it will increase the width and height of the output image.
14+
1215
## [2.6.2] - 2024-03-26
1316

1417
### Fixed

example/bbox.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ async function main() {
2323

2424
// const bbox = resvg.innerBBox()
2525
const bbox = resvg.getBBox()
26-
if (bbox) resvg.cropByBBox(bbox)
26+
if (bbox) resvg.cropByBBox(bbox, 0)
27+
2728
const pngData = resvg.render()
2829
const pngBuffer = pngData.asPng()
2930

index.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,12 @@ export class Resvg {
7171
/**
7272
* Use a given `BBox` to crop the svg. Currently this method simply changes
7373
* the viewbox/size of the svg and do not move the elements for simplicity
74+
* # Arguments
75+
* `bbox` - The bounding box to crop to
76+
* `padding` - Optional bleed area around the crop box (default: 0.0)
7477
*/
75-
cropByBBox(bbox: BBox): void
78+
// cropByBBox(bbox: BBox): void
79+
cropByBBox(bbox: BBox, padding?: number): void
7680

7781
imagesToResolve(): Array<string>
7882
resolveImage(href: string, buffer: Buffer): void

js-binding.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,12 @@ export declare class Resvg {
3131
/**
3232
* Use a given `BBox` to crop the svg. Currently this method simply changes
3333
* the viewbox/size of the svg and do not move the elements for simplicity
34+
*
35+
* # Arguments
36+
* * `bbox` - The bounding box to crop to
37+
* * `padding` - Optional bleed area around the crop box (default: 0.0)
3438
*/
35-
cropByBBox(bbox: BBox): void
39+
cropByBBox(bbox: BBox, padding?: number | undefined | null): void
3640
imagesToResolve(): Array<string>
3741
resolveImage(href: string, buffer: Buffer): void
3842
/** Get the SVG width */

src/lib.rs

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -235,14 +235,21 @@ impl Resvg {
235235
#[napi(js_name = cropByBBox)]
236236
/// Use a given `BBox` to crop the svg. Currently this method simply changes
237237
/// the viewbox/size of the svg and do not move the elements for simplicity
238-
pub fn crop_by_bbox(&mut self, bbox: &BBox) {
238+
///
239+
/// # Arguments
240+
/// * `bbox` - The bounding box to crop to
241+
/// * `padding` - Optional bleed area around the crop box (default: 0.0)
242+
pub fn crop_by_bbox(&mut self, bbox: &BBox, padding: Option<f64>) {
239243
if !bbox.width.is_finite() || !bbox.height.is_finite() {
240244
return;
241245
}
242-
let width = bbox.width as f32;
243-
let height = bbox.height as f32;
244-
self.tree.view_box.rect =
245-
usvg::NonZeroRect::from_xywh(bbox.x as f32, bbox.y as f32, width, height).unwrap();
246+
let padding = padding.unwrap_or(0.0) as f32;
247+
let x = (bbox.x as f32) - padding;
248+
let y = (bbox.y as f32) - padding;
249+
let width = (bbox.width as f32) + (padding * 2.0);
250+
let height = (bbox.height as f32) + (padding * 2.0);
251+
252+
self.tree.view_box.rect = usvg::NonZeroRect::from_xywh(x, y, width, height).unwrap();
246253
self.tree.size = usvg::Size::from_wh(width, height).unwrap();
247254
}
248255

@@ -373,14 +380,21 @@ impl Resvg {
373380
#[wasm_bindgen(js_name = cropByBBox)]
374381
/// Use a given `BBox` to crop the svg. Currently this method simply changes
375382
/// the viewbox/size of the svg and do not move the elements for simplicity
376-
pub fn crop_by_bbox(&mut self, bbox: &BBox) {
383+
///
384+
/// # Arguments
385+
/// * `bbox` - The bounding box to crop to
386+
/// * `padding` - Optional bleed area around the crop box (default: 0.0)
387+
pub fn crop_by_bbox(&mut self, bbox: &BBox, padding: Option<f64>) {
377388
if !bbox.width.is_finite() || !bbox.height.is_finite() {
378389
return;
379390
}
380-
let width = bbox.width as f32;
381-
let height = bbox.height as f32;
382-
self.tree.view_box.rect =
383-
usvg::NonZeroRect::from_xywh(bbox.x as f32, bbox.y as f32, width, height).unwrap();
391+
let padding = padding.unwrap_or(0.0) as f32;
392+
let x = (bbox.x as f32) - padding;
393+
let y = (bbox.y as f32) - padding;
394+
let width = (bbox.width as f32) + (padding * 2.0);
395+
let height = (bbox.height as f32) + (padding * 2.0);
396+
397+
self.tree.view_box.rect = usvg::NonZeroRect::from_xywh(x, y, width, height).unwrap();
384398
self.tree.size = usvg::Size::from_wh(width, height).unwrap();
385399
}
386400

wasm/index.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ export declare const Resvg: {
9898
toString(): string;
9999
innerBBox(): BBox | undefined;
100100
getBBox(): BBox | undefined;
101-
cropByBBox(bbox: BBox): void;
101+
cropByBBox(bbox: BBox, padding?: number | undefined): void;
102102
imagesToResolve(): any[];
103103
resolveImage(href: string, buffer: Uint8Array): void;
104104
readonly height: number;

wasm/index.html

Lines changed: 48 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,23 @@
269269
repeating-conic-gradient(#808080 0% 25%, transparent 0% 50%)
270270
50% / var(--chessboard-size) var(--chessboard-size);
271271
}
272+
273+
#bleed-size::-webkit-slider-thumb {
274+
anchor-name: --foo;
275+
}
276+
#bleed-size:after {
277+
content: attr(data-value);
278+
position: absolute;
279+
z-index: 999999;
280+
font-size: 18px;
281+
position-anchor: --foo;
282+
position-area: top center;
283+
background-color: #fff;
284+
border: 1px solid #999;
285+
border-radius: 4px;
286+
padding: .25em .6em;
287+
margin-bottom: 8px;
288+
}
272289
</style>
273290
<script>
274291
const fontList = [
@@ -288,20 +305,7 @@
288305

289306
const svgList = [{
290307
name: 'Hello resvg-js',
291-
svgString: `<svg width="600" height="300" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
292-
<defs>
293-
<linearGradient x1="0%" y1="45%" x2="100%" y2="55%" id="b">
294-
<stop stop-color="#FF8253" offset="0%"/>
295-
<stop stop-color="#DA1BC6" offset="100%"/>
296-
</linearGradient>
297-
<path id="a" d="M0 0h600v300H0z"/>
298-
</defs>
299-
<g fill="none" fill-rule="evenodd">
300-
<use fill="url(#b)" xlink:href="#a"/>
301-
<text x="50%" y="39%" font-size="58" fill="#FFF" dominant-baseline="middle" text-anchor="middle">Hello resvg-js</text>
302-
<text x="50%" y="62%" font-size="38" fill="#FFF" dominant-baseline="middle" text-anchor="middle">高性能 SVG 渲染引擎和工具包</text>
303-
</g>
304-
</svg>`
308+
svgString: `<svg version="1.0" xmlns="http://www.w3.org/2000/svg" width="800.000000" height="800.000000" viewBox="0 0 800.000000 800.000000"><g transform="translate(0.000000,800.000000) scale(0.100000,-0.100000)" fill="#000000" stroke="none"><path d="M3770 6484 c-241 -29 -397 -62 -574 -121 -732 -242 -1303 -798 -1559 -1518 -103 -290 -142 -523 -141 -850 2 -409 78 -741 254 -1100 230 -470 595 -848 1051 -1089 261 -138 500 -216 824 -271 128 -22 462 -30 615 -16 783 76 1472 494 1885 1146 75 119 194 364 239 494 171 490 189 1021 52 1522 -19 69 -46 155 -60 189 -14 35 -26 67 -26 71 0 4 -24 59 -53 121 -125 264 -280 483 -491 693 -220 219 -393 344 -661 475 -236 115 -491 196 -745 236 -101 15 -525 28 -610 18z m515 -299 c345 -49 683 -173 955 -352 717 -471 1091 -1326 955 -2183 -141 -880 -782 -1570 -1659 -1786 -184 -45 -344 -64 -536 -64 -759 2 -1443 363 -1856 981 -190 285 -316 641 -355 1004 -14 134 -6 432 15 560 109 641 449 1160 992 1511 247 159 596 286 904 328 140 19 457 20 585 1z"/><path d="M3588 5860 c-248 -52 -453 -136 -654 -268 -116 -76 -146 -131 -110 -200 8 -15 203 -215 433 -444 l419 -418 45 0 c32 0 55 7 73 21 58 45 56 28 56 656 0 556 -1 579 -20 610 -39 65 -91 74 -242 43z"/><path d="M4221 5862 c-19 -10 -43 -34 -53 -53 -17 -31 -18 -71 -18 -602 0 -604 0 -608 49 -651 12 -11 42 -21 72 -24 l51 -5 419 419 c230 230 426 433 434 451 36 75 -4 135 -147 221 -192 116 -401 198 -617 241 -127 26 -147 26 -190 3z"/><path d="M2516 5180 c-41 -13 -79 -52 -141 -145 -135 -206 -256 -526 -272 -725 -5 -72 -4 -77 21 -107 15 -18 44 -37 64 -43 40 -11 1127 -15 1181 -4 37 8 87 56 94 91 12 61 -3 79 -436 511 -232 232 -430 422 -439 422 -9 0 -22 2 -30 4 -7 2 -26 0 -42 -4z"/><path d="M5405 5176 c-45 -21 -862 -845 -870 -878 -10 -40 12 -95 48 -120 l32 -23 580 -3 c379 -2 592 1 616 7 85 25 103 89 71 247 -42 202 -123 409 -234 592 -67 111 -120 169 -167 182 -44 12 -39 12 -76 -4z"/><path d="M2164 3831 c-72 -44 -79 -91 -40 -263 48 -208 139 -428 246 -593 93 -143 142 -179 213 -156 49 17 873 845 882 887 9 44 -17 99 -57 124 -32 19 -49 20 -623 20 -563 0 -591 -1 -621 -19z"/><path d="M4592 3830 c-40 -24 -66 -81 -58 -124 9 -46 846 -881 893 -891 64 -13 105 16 183 130 125 185 219 407 270 640 33 149 25 199 -39 242 l-34 23 -591 0 c-575 0 -592 -1 -624 -20z"/><path d="M3675 3460 c-11 -4 -208 -197 -437 -427 -458 -460 -450 -449 -412 -528 35 -74 310 -229 539 -304 112 -37 311 -81 363 -81 36 0 81 28 102 63 19 31 20 54 20 612 0 558 -1 581 -20 612 -32 53 -100 77 -155 53z"/><path d="M4212 3450 c-63 -39 -62 -31 -62 -656 0 -626 -1 -615 64 -655 41 -25 76 -24 215 7 214 47 400 119 573 220 156 91 209 163 174 235 -9 19 -202 220 -430 448 -403 403 -414 413 -457 418 -33 3 -52 -1 -77 -17z"/></g></svg>`
305309
},
306310
{
307311
name: 'GitHub Octocat',
@@ -384,11 +388,10 @@
384388
resvgOpts.font.fontBuffers = [buffer]
385389
fontList[0].buffer = buffer
386390
}
387-
await svg2png(INIT_INPUT_SVG_STRING, resvgOpts)
391+
await svg2png(INIT_INPUT_SVG_STRING, resvgOpts, false, 20)
388392

389-
async function svg2png(svgString, opts, hasCrop) {
393+
async function svg2png(svgString, opts, hasCrop = false, bleedSize = 0) {
390394
const svg = svgString ? svgString : svgInputElement.value.trim()
391-
392395
if (!svg) {
393396
alert('SVG is empty')
394397
return
@@ -413,10 +416,12 @@
413416
}
414417
}
415418

416-
const innerBBox = resvgJS.innerBBox()
419+
// const innerBBox = resvgJS.innerBBox()
417420
const bbox = resvgJS.getBBox()
418421

419-
if (hasCrop && bbox) resvgJS.cropByBBox(bbox)
422+
if (hasCrop && bbox) resvgJS.cropByBBox(bbox, bleedSize)
423+
console.info('Simplified svg string \n', resvgJS.toString())
424+
420425
const pngData = resvgJS.render()
421426
const pngBuffer = pngData.asPng()
422427

@@ -430,6 +435,7 @@
430435
addMultipleEventListener(svgInputElement, ['change', 'keyup', 'paste'], async function (event) {
431436
SVG_STRING = event.target.value.trim()
432437
const hasCrop = cropElement.checked
438+
const bleedSize = bleedSizeElement.value
433439
if (!SVG_STRING) return
434440

435441
// 通过 svgString 更新 inputSvgElement
@@ -442,7 +448,7 @@
442448
}
443449

444450
await checkGetFontBuffer()
445-
svg2png(SVG_STRING, resvgOpts, hasCrop)
451+
svg2png(SVG_STRING, resvgOpts, hasCrop, bleedSize)
446452
})
447453

448454
function addMultipleEventListener(element, events, handler) {
@@ -453,6 +459,7 @@
453459
const colorPickerAlphaElement = document.querySelector('#color-picker-alpha')
454460
const svgSizeElement = document.querySelector('#svg-size')
455461
const cropElement = document.querySelector('#crop-by-bbox')
462+
const bleedSizeElement = document.querySelector('#bleed-size')
456463
const svgSelectElement = document.querySelector('#slt-svg')
457464
const fontSelectElement = document.querySelector('#slt-font')
458465

@@ -492,13 +499,16 @@
492499
},
493500
}
494501

495-
await svg2png(SVG_STRING, resvgOpts)
502+
const hasCrop = cropElement.checked
503+
const bleedSize = bleedSizeElement.value
504+
await svg2png(SVG_STRING, resvgOpts, hasCrop, bleedSize)
496505
}
497506
}
498507
})
499508

500509
function onChangeColor() {
501510
const hasCrop = cropElement.checked
511+
const bleedSize = bleedSizeElement.value
502512
const style = window.getComputedStyle(document.querySelector('html'), ':before')
503513
const color = style.getPropertyValue('color')
504514
const rgb_color = convertColorToRGBA(color)
@@ -507,7 +517,7 @@
507517
console.info('convert to rgba()\n', rgb_color)
508518

509519
resvgOpts.background = rgb_color
510-
svg2png(null, resvgOpts, hasCrop)
520+
svg2png(null, resvgOpts, hasCrop, bleedSize)
511521
}
512522

513523
colorPickerElement.addEventListener('input', function (event) {
@@ -536,6 +546,7 @@
536546
svgSizeElement.addEventListener('change', function (event) {
537547
const value = event.target.value
538548
const hasCrop = cropElement.checked
549+
const bleedSize = bleedSizeElement.value
539550
if (!value) {
540551
Object.assign(resvgOpts, {
541552
fitTo: {
@@ -557,12 +568,21 @@
557568
})
558569
}
559570

560-
svg2png(null, resvgOpts, hasCrop)
571+
svg2png(null, resvgOpts, hasCrop, bleedSize)
561572
})
562573

563574
cropElement.addEventListener('change', function (event) {
564575
const hasCrop = event.target.checked
565-
svg2png(null, resvgOpts, hasCrop)
576+
const bleedSize = bleedSizeElement.value
577+
svg2png(null, resvgOpts, hasCrop, bleedSize)
578+
})
579+
580+
bleedSizeElement.addEventListener('input', function (event) {
581+
const value = event.target.value
582+
event.target.setAttribute('data-value', value)
583+
const hasCrop = cropElement.checked
584+
const bleedSize = value
585+
svg2png(null, resvgOpts, hasCrop, bleedSize)
566586
})
567587

568588
async function checkGetFontBuffer() {
@@ -685,6 +705,10 @@ <h1 class="site-title">resvg-js playground</h1>
685705
<label for="crop-by-bbox">Crop by BBox:</label>
686706
<input type="checkbox" name="crop-by-bbox" id="crop-by-bbox">
687707
</div>
708+
<div class="opts-cell">
709+
<label for="bleed-size">Bleed Size:</label>
710+
<input type="range" name="bleed-size" id="bleed-size" min="0" max="1000" value="24" data-value="24">
711+
</div>
688712
</div>
689713
<div class="box">
690714
<div class="cell">

wasm/index.js

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -388,11 +388,16 @@ var Resvg = class {
388388
/**
389389
* Use a given `BBox` to crop the svg. Currently this method simply changes
390390
* the viewbox/size of the svg and do not move the elements for simplicity
391+
*
392+
* # Arguments
393+
* * `bbox` - The bounding box to crop to
394+
* * `padding` - Optional bleed area around the crop box (default: 0.0)
391395
* @param {BBox} bbox
396+
* @param {number | undefined} [padding]
392397
*/
393-
cropByBBox(bbox) {
398+
cropByBBox(bbox, padding) {
394399
_assertClass(bbox, BBox);
395-
wasm.resvg_cropByBBox(this.__wbg_ptr, bbox.__wbg_ptr);
400+
wasm.resvg_cropByBBox(this.__wbg_ptr, bbox.__wbg_ptr, !isLikeNone(padding), isLikeNone(padding) ? 0 : padding);
396401
}
397402
/**
398403
* @returns {Array<any>}

0 commit comments

Comments
 (0)