I'd do it like this: on('ready',()=>{
const minPageX=10;
const minPageY=10;
const mapOnlyBounds=true;
const fuzzFactor=1.0e-4;
const bKeys=['left','top','width','height','rotation'];
const d2r = (degrees) => degrees * (Math.PI/180);
//const r2d = (radians) => radians * (180/Math.PI);
const rotateOPA = (o,p,a) => {
const r = d2r(a);
const s = Math.sin(r);
const c = Math.cos(r);
let pp = {
x: p.x-o.x,
y: p.y-o.y
};
return {
x: (pp.x * c - pp.y * s)+o.x,
y: (pp.x * s + pp.y * c)+o.y
};
};
const oBounds = (o)=>{
let oB = _.reduce(bKeys,(mB,k)=>Object.assign(mB,{[k]:o.get(k)}),{});
const oC = {x: oB.left, y: oB.top};
const oR = o.get('rotation');
oB = {
left: oB.left-Math.floor(oB.width/2),
top: oB.top-Math.floor(oB.height/2),
right: oB.left+Math.ceil(oB.width/2),
bottom: oB.top+Math.ceil(oB.height/2)
};
if(oR % 180){
let p1 = rotateOPA(oC, {x:oB.left, y:oB.top}, oR),
p2 = rotateOPA(oC, {x:oB.right, y:oB.top}, oR),
p3 = rotateOPA(oC, {x:oB.left, y:oB.bottom}, oR),
p4 = rotateOPA(oC, {x:oB.right, y:oB.bottom}, oR);
oB = {
left: Math.min(p1.x,p2.x,p3.x,p4.x),
top: Math.min(p1.y,p2.y,p3.y,p4.y),
right: Math.max(p1.x,p2.x,p3.x,p4.x),
bottom: Math.max(p1.y,p2.y,p3.y,p4.y)
};
}
return oB;
};
const translateObjs = (objs,transform) => objs.forEach(
(o) => o.set(Object.keys(transform).reduce(
(m,k) => Object.assign(m, {[k]:o.get(k)-transform[k]}), {})
)
);
const resizePage = _.debounce((pageid) =>{
const page = getObj('page',pageid);
let count = 0;
if(page){
const objs = findObjs({pageid}); // will only be Graphics, Paths, Text
let bounds = _.reduce(objs,(m,o)=>{
if(mapOnlyBounds && o.get('layer')!=='map'){
return m;
}
count++;
let oB = oBounds(o);
switch(o.get('type')){
case 'text':
case 'path':
case 'graphic':
return {
left: Math.min(m.left,oB.left),
top: Math.min(m.top,oB.top),
right: Math.max(m.right,oB.right),
bottom: Math.max(m.bottom,oB.bottom)
};
}
return m;
},{left:Infinity,top:Infinity,right:-Infinity,bottom:-Infinity});
if(count){
if(bounds.left || bounds.top){
translateObjs(objs, { left: bounds.left, top: bounds.top});
bounds={
left: 0,
top: 0,
right: bounds.right-bounds.left,
bottom: bounds.bottom-bounds.top,
};
}
let uX=Math.max(minPageX,Math.ceil((bounds.right-fuzzFactor)/70));
let uY=Math.max(minPageY,Math.ceil((bounds.bottom-fuzzFactor)/70));
if(uX !== page.get('width') || uY !== page.get('height') ){
page.set({
width: uX,
height: uY
});
}
}
}
},50);
const handleMapLayerGraphic = (event, obj, prev)=>{
if('map' === obj.get('layer')){
switch(event){
case 'add:graphic':
_.defer(()=>resizePage((getObj('graphic',obj.id)||{get:()=>{}}).get('pageid')));
break;
case 'change:graphic':
if(bKeys.reduce((m,k)=>m || obj.get(k)!=prev[k],false)){
_.defer(()=>resizePage(obj.get('pageid')));
}
break;
case 'destroy:graphic':
_.defer(()=>resizePage(obj.get('pageid')));
break;
}
}
};
['add:graphic','change:graphic','destroy:graphic'].forEach((e)=>on(e,(o,p)=>handleMapLayerGraphic(e,o,p)));
});
This will automatically adjust the size of the page whenever you change something on the map layer. It will calculate the new bounds of the page based only on things on the map layer, then it will shift everything on the page to be at the right place. This includes tokens, text, paths, and Dynamic Lighting paths. Some Caveats: I suggest not moving something on the map layer if you've lined up Dynamic Lighting Lines with it, as this script doesn't know which are associated with it, so they will get moved elsewhere. If you orphan any of the things on the objects, gm or walls layers by moving the map out from under them, they'll remain off the page and will only show up again when you move something on the map layer out underneath them. This might be an interesting way to hide encounters, but just be aware of it. The minimum size it will set a page to is 10x10. This is to prevent deleting the last object on the maps later and then not having anywhere to drop something. If you remove the last item from the map layer, it won't resize. If you have a map you don't want shrinking, be sure to put some markers on the map layer in the corners. Changes that happen because of the API won't trigger.