@pinepaper.studio/mcp-server 1.6.15 → 1.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.de.md +1 -1
- package/README.es.md +1 -1
- package/README.fr.md +1 -1
- package/README.hi.md +1 -1
- package/README.ja.md +1 -1
- package/README.ko.md +1 -1
- package/README.md +64 -4
- package/README.pt-BR.md +1 -1
- package/README.zh-CN.md +1 -1
- package/dist/build-info.json +2 -2
- package/dist/ontology/upstream-vocabulary.d.ts +2 -2
- package/dist/ontology/upstream-vocabulary.js +2 -2
- package/dist/tools/definitions.d.ts.map +1 -1
- package/dist/tools/definitions.js +105 -8
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/engine-surface.d.ts +1 -1
- package/dist/tools/engine-surface.d.ts.map +1 -1
- package/dist/tools/engine-surface.js +30 -9
- package/dist/tools/engine-surface.js.map +1 -1
- package/dist/tools/handlers/camera.d.ts.map +1 -1
- package/dist/tools/handlers/camera.js +11 -0
- package/dist/tools/handlers/camera.js.map +1 -1
- package/dist/tools/handlers/generate.d.ts +324 -0
- package/dist/tools/handlers/generate.d.ts.map +1 -0
- package/dist/tools/handlers/generate.js +264 -0
- package/dist/tools/handlers/generate.js.map +1 -0
- package/dist/tools/handlers.d.ts.map +1 -1
- package/dist/tools/handlers.js +39 -4
- package/dist/tools/handlers.js.map +1 -1
- package/dist/tools/minimal-descriptions.d.ts.map +1 -1
- package/dist/tools/minimal-descriptions.js +3 -0
- package/dist/tools/minimal-descriptions.js.map +1 -1
- package/dist/tools/shape-params.d.ts +2 -2
- package/dist/tools/shape-params.js +2 -2
- package/dist/tools/stick-vocabulary.d.ts +1 -1
- package/dist/tools/stick-vocabulary.js +1 -1
- package/dist/tools/toolkits.d.ts.map +1 -1
- package/dist/tools/toolkits.js +3 -0
- package/dist/tools/toolkits.js.map +1 -1
- package/dist/types/code-generator.d.ts +15 -0
- package/dist/types/code-generator.d.ts.map +1 -1
- package/dist/types/code-generator.js +279 -21
- package/dist/types/code-generator.js.map +1 -1
- package/dist/types/schemas.d.ts +527 -82
- package/dist/types/schemas.d.ts.map +1 -1
- package/dist/types/schemas.js +19 -7
- package/dist/types/schemas.js.map +1 -1
- package/dist/vendor/design/design-style.js +1 -1
- package/manifest.json +13 -1
- package/package.json +1 -1
|
@@ -917,8 +917,15 @@ if (_flagged && _flagged.item && _flagged.item.data) Object.assign(_flagged.item
|
|
|
917
917
|
+ modIgnored.filter((k) => MODIFY_DID_YOU_MEAN[k]).map((k) => ` Did you mean ${MODIFY_DID_YOU_MEAN[k]} (not ${k})?`).join(''))}`;
|
|
918
918
|
code += `
|
|
919
919
|
app.historyManager.saveState();
|
|
920
|
+
${properties.content !== undefined ? `
|
|
921
|
+
// CONTENT UNDER A CONTENT TRACK (round 10, 10.13): the keyframes set the text
|
|
922
|
+
// on every frame, so a direct edit is overwritten at once — it answered
|
|
923
|
+
// success and changed nothing visible.
|
|
924
|
+
const __ctItem = app.getItemById('${itemId}');
|
|
925
|
+
const __ctKeys = (__ctItem && __ctItem.data && Array.isArray(__ctItem.data.keyframes))
|
|
926
|
+
? __ctItem.data.keyframes.filter(function(k) { return k && k.properties && Object.prototype.hasOwnProperty.call(k.properties, 'content'); }).length : 0;` : ''}
|
|
920
927
|
${modDirection ? emitDirectionCheck(`app.getItemById('${itemId}')`, modDirection) : ''}${modTabular ? emitTabularCheck(`app.getItemById('${itemId}')`) : ''}${modStrokePos ? emitStrokePositionCheck(`app.getItemById('${itemId}')`, modStrokePos) : ''}
|
|
921
|
-
return { success: true, itemId: '${itemId}'${modFit ? ', textFit: __textFit' : ''}${modDirection ? ', direction: __direction' : ''}${modTabular ? ', tabularFigures: __tabular' : ''}${modStrokePos ? ', strokePosition: __strokePos' : ''}${affine ? `, affine: ${JSON.stringify({ skewX, skewY, matrix, note: 'applied to the current shape: calling again compounds it. To undo, apply the inverse (negative skew, or the inverse matrix).' })}` : ''}${modLifetime ? ', lifetime: __lifetime' : ''}${modFont ? ', ...(__font ? { font: __font } : {})' : ''}${modWarning} };`;
|
|
928
|
+
return { success: true, itemId: '${itemId}'${properties.content !== undefined ? `, ...(__ctKeys ? { contentTrackWarning: 'this text\\'s content is keyframed (' + __ctKeys + ' key(s)), so the track sets it on every frame and this edit does not show. Change the content keys with pinepaper_keyframe_animate (append: true replaces same-time keys), or animate another property.' } : {})` : ''}${modFit ? ', textFit: __textFit' : ''}${modDirection ? ', direction: __direction' : ''}${modTabular ? ', tabularFigures: __tabular' : ''}${modStrokePos ? ', strokePosition: __strokePos' : ''}${affine ? `, affine: ${JSON.stringify({ skewX, skewY, matrix, note: 'applied to the current shape: calling again compounds it. To undo, apply the inverse (negative skew, or the inverse matrix).' })}` : ''}${modLifetime ? ', lifetime: __lifetime' : ''}${modFont ? ', ...(__font ? { font: __font } : {})' : ''}${modWarning} };`;
|
|
922
929
|
// Wrapped, because the body now RETURNS early on a miss rather than falling
|
|
923
930
|
// through to an unconditional success. Async only when a font must load
|
|
924
931
|
// before modifyItem re-measures the text.
|
|
@@ -1235,7 +1242,7 @@ if (!_target) {
|
|
|
1235
1242
|
return { success: false, error: ${JSON.stringify(itemId)} + ' is not on the canvas, so ' + ${JSON.stringify(what)} + ' would have done nothing. Check the id with pinepaper_get_items — create returns the registry id, not the Paper id.' };
|
|
1236
1243
|
}`.trim();
|
|
1237
1244
|
}
|
|
1238
|
-
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false, timeOffset, clipInPoint, clipOutPoint, timeUnits) {
|
|
1245
|
+
function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false, timeOffset, clipInPoint, clipOutPoint, timeUnits, append = false) {
|
|
1239
1246
|
const keyframesJson = JSON.stringify(normalizeKeyframePositions(keyframes), null, 2);
|
|
1240
1247
|
const calculatedDuration = duration || Math.max(...keyframes.map(k => k.time));
|
|
1241
1248
|
const opts = { duration: calculatedDuration, loop };
|
|
@@ -1247,6 +1254,10 @@ function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false,
|
|
|
1247
1254
|
opts.clipOutPoint = clipOutPoint;
|
|
1248
1255
|
if (timeUnits !== undefined)
|
|
1249
1256
|
opts.timeUnits = timeUnits;
|
|
1257
|
+
// addAnimation SETS the item's track; mode 'merge' adds to it (FxTool
|
|
1258
|
+
// 2475333b). An engine without merge ignores the option and replaces.
|
|
1259
|
+
if (append)
|
|
1260
|
+
opts.mode = 'merge';
|
|
1250
1261
|
// A FADE ON AN AUDIO ITEM WAS ACCEPTED AND NEVER HEARD. addAnimation keyframes
|
|
1251
1262
|
// the audio item's sentinel, and the mix reads one static audioGain per clip,
|
|
1252
1263
|
// so {audioGain: 0→1} answered success, duration 15, and exported at a flat
|
|
@@ -1285,7 +1296,10 @@ function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false,
|
|
|
1285
1296
|
const gaps = keyframes
|
|
1286
1297
|
.map((k, i) => ({ time: k.time, missing: allProps.filter((p) => !propSets[i].includes(p)) }))
|
|
1287
1298
|
.filter((g) => g.missing.length);
|
|
1288
|
-
|
|
1299
|
+
// With append the gaps that matter are the MERGED track's (an earlier key
|
|
1300
|
+
// with no scale beside a new key with one), so that check runs in the page
|
|
1301
|
+
// on the result instead (below).
|
|
1302
|
+
const gapNote = !append && gaps.length && keyframes.length > 1
|
|
1289
1303
|
? `, propertyGaps: ${JSON.stringify(gaps.slice(0, 10))}, gapWarning: ${JSON.stringify(`${gaps.length} keyframe(s) leave out a property other keys animate (${[...new Set(gaps.flatMap((g) => g.missing))].join(', ')}). The engine can break that property's track at such a key; repeat the value on every key — hold it by repeating the previous value.`)}`
|
|
1290
1304
|
: '';
|
|
1291
1305
|
const first = [...keyframes].sort((a, b) => a.time - b.time)[0];
|
|
@@ -1302,8 +1316,25 @@ function generateKeyframeAnimateCode(itemId, keyframes, duration, loop = false,
|
|
|
1302
1316
|
// Apply keyframe animation to ${itemId}
|
|
1303
1317
|
(function() {
|
|
1304
1318
|
${requireItem(itemId, 'the animation')}${audioLevelGuard}
|
|
1319
|
+
// The track as it was, so the result can say what happened to it: merged
|
|
1320
|
+
// (every earlier key still there) or replaced. That is the evidence of
|
|
1321
|
+
// merge support — an older engine takes the option and replaces anyway.
|
|
1322
|
+
const __ki = _target && (_target.item || _target);
|
|
1323
|
+
const __prev = (__ki && __ki.data && Array.isArray(__ki.data.keyframes)) ? __ki.data.keyframes.map(function(k) { return k && k.time; }) : [];
|
|
1305
1324
|
app.addAnimation('${itemId}', ${keyframesJson}, ${JSON.stringify(opts)});
|
|
1306
|
-
|
|
1325
|
+
const __now = (__ki && __ki.data && Array.isArray(__ki.data.keyframes)) ? __ki.data.keyframes.map(function(k) { return k && k.time; }) : [];
|
|
1326
|
+
const __kept = __prev.every(function(t) { return __now.indexOf(t) >= 0; });${append ? `
|
|
1327
|
+
// Property gaps across the merged track (the same check a single call makes
|
|
1328
|
+
// on its own keys at generation time).
|
|
1329
|
+
const __all = (__ki && __ki.data && Array.isArray(__ki.data.keyframes)) ? __ki.data.keyframes : [];
|
|
1330
|
+
const __props = __all.reduce(function(acc, k) { Object.keys((k && k.properties) || {}).forEach(function(p) { if (acc.indexOf(p) < 0) acc.push(p); }); return acc; }, []);
|
|
1331
|
+
const __gaps = __all.length > 1 ? __all.map(function(k) { const have = Object.keys((k && k.properties) || {}); return { time: k && k.time, missing: __props.filter(function(p) { return have.indexOf(p) < 0; }) }; }).filter(function(g) { return g.missing.length; }) : [];` : ''}
|
|
1332
|
+
const __track = !__prev.length ? null
|
|
1333
|
+
: ${append ? `(__kept
|
|
1334
|
+
? { appended: true, previousKeys: __prev.length, keys: __now.length }
|
|
1335
|
+
: { appended: false, replaced: true, previousKeys: __prev.length, warning: 'append was asked for, but this studio replaced the existing track (' + __prev.length + ' key(s)) instead of adding to it: it has no merge mode. Put every key in one keyframe_animate call.' })`
|
|
1336
|
+
: `{ replaced: true, previousKeys: __prev.length, note: 'the item already had a keyframe track (' + __prev.length + ' key(s)); this call replaced it. To add keys to it instead, pass append: true.' }`};
|
|
1337
|
+
return { success: true, itemId: '${itemId}', duration: ${calculatedDuration}, loop: ${loop}${append ? ', append: true' : ''}, ...(__track ? { track: __track } : {})${append ? `, ...(__gaps.length ? { propertyGaps: __gaps.slice(0, 10), gapWarning: __gaps.length + ' key(s) of the merged track leave out a property other keys animate (' + __gaps.reduce(function(a, g) { g.missing.forEach(function(p) { if (a.indexOf(p) < 0) a.push(p); }); return a; }, []).join(', ') + '). The engine can break that property at such a key; give each key the full set — hold a value by repeating it.' } : {})` : ''}${timeOffset !== undefined ? `, timeOffset: ${timeOffset}` : ''}${clipInPoint !== undefined ? `, clipInPoint: ${clipInPoint}` : ''}${clipOutPoint !== undefined ? `, clipOutPoint: ${clipOutPoint}` : ''}${easingNote}${loopNote}${gapNote} };
|
|
1307
1338
|
})();
|
|
1308
1339
|
`.trim();
|
|
1309
1340
|
}
|
|
@@ -2283,7 +2314,7 @@ export class PinePaperCodeGenerator {
|
|
|
2283
2314
|
*/
|
|
2284
2315
|
generateKeyframeAnimate(input) {
|
|
2285
2316
|
const validated = KeyframeAnimateInputSchema.parse(input);
|
|
2286
|
-
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop, validated.timeOffset, validated.clipInPoint, validated.clipOutPoint, validated.timeUnits);
|
|
2317
|
+
return generateKeyframeAnimateCode(validated.itemId, validated.keyframes, validated.duration, validated.loop, validated.timeOffset, validated.clipInPoint, validated.clipOutPoint, validated.timeUnits, validated.append);
|
|
2287
2318
|
}
|
|
2288
2319
|
/**
|
|
2289
2320
|
* Generate code for importing / retargeting a motion-capture clip
|
|
@@ -3945,7 +3976,7 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
3945
3976
|
}
|
|
3946
3977
|
generateAgentExport(input) {
|
|
3947
3978
|
const validated = AgentExportInputSchema.parse(input);
|
|
3948
|
-
const { platform, format, quality, framing, duration, estimateOnly, scale, fps, pdf: pdfOpts, region, time: stillTime, maxBytes, loop: gifLoop, broadcast, broadcastHeadroom, bitrate, minBitrate, bitrateMode, transparent, alphaQuantizer, ad: adOpts } = validated;
|
|
3979
|
+
const { platform, format, quality, framing, duration, estimateOnly, scale, fps, pdf: pdfOpts, region, time: stillTime, maxBytes, loop: gifLoop, broadcast, broadcastHeadroom, bitrate, minBitrate, bitrateMode, transparent, alphaQuantizer, deterministic, ad: adOpts } = validated;
|
|
3949
3980
|
// Encoder options (FxTool 16719759); the schema keeps them to mp4 / webm.
|
|
3950
3981
|
const videoEncodeOpts = {};
|
|
3951
3982
|
if (broadcast)
|
|
@@ -3963,6 +3994,10 @@ return { success: true, action: 'seek', time: ${op.time || 0} };
|
|
|
3963
3994
|
videoEncodeOpts.minBitrate = minBitrate;
|
|
3964
3995
|
if (bitrateMode !== undefined)
|
|
3965
3996
|
videoEncodeOpts.bitrateMode = bitrateMode;
|
|
3997
|
+
// Byte-identical repeats (FxTool 3b335567): the container timestamps are
|
|
3998
|
+
// the only bytes that varied, and this pins them.
|
|
3999
|
+
if (deterministic)
|
|
4000
|
+
videoEncodeOpts.deterministic = true;
|
|
3966
4001
|
// A transparent WebM (FxTool 1833b397) rides the store route too.
|
|
3967
4002
|
const webmAlpha = format === 'webm' && transparent === true;
|
|
3968
4003
|
if (webmAlpha) {
|
|
@@ -4968,6 +5003,18 @@ ${stillTime !== undefined ? `
|
|
|
4968
5003
|
|
|
4969
5004
|
if (result && result.success) Object.assign(result, fidelity(format));
|
|
4970
5005
|
if (result && result.success && __fit) result.platformFit = __fit;
|
|
5006
|
+
// A LANDSCAPE CANVAS FOR A PORTRAIT PLATFORM (or the reverse) is not an
|
|
5007
|
+
// aspect nuance but the wrong deliverable: tiktok on a 16:9 canvas came out
|
|
5008
|
+
// 1920x1080 with only a note (round 10, 10.7). Said where a caller looks.
|
|
5009
|
+
if (result && result.success && __fit) {
|
|
5010
|
+
const __pw = __fit.preset.split('x').map(Number), __uw = __fit.used.split('x').map(Number);
|
|
5011
|
+
if ((__pw[0] > __pw[1]) !== (__uw[0] > __uw[1]) && __pw[0] !== __pw[1]) {
|
|
5012
|
+
result.fidelity = result.fidelity || { warnings: [] };
|
|
5013
|
+
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'platform_orientation_mismatch',
|
|
5014
|
+
message: platform + ' is ' + (__pw[0] > __pw[1] ? 'landscape' : 'portrait') + ' (' + __fit.preset + '), but the canvas is ' + (__uw[0] > __uw[1] ? 'landscape' : 'portrait') + ', so the file is ' + __fit.used + '. For a ' + platform + ' deliverable, resize the canvas to ' + __fit.preset + ' (pinepaper_set_canvas_size) and re-lay the scene for it; the export does not reframe a scene.' }]);
|
|
5015
|
+
if (result.fidelity.note) delete result.fidelity.note;
|
|
5016
|
+
}
|
|
5017
|
+
}
|
|
4971
5018
|
// AUDIO TIMING, WHERE THE STUDIO REPORTS IT (6.27). An MP4's AAC track used
|
|
4972
5019
|
// to start ~44 ms late and run past the picture — a 6.000 s bumper with sound
|
|
4973
5020
|
// then failed its cap. The engine now reports the track; it is surfaced as
|
|
@@ -5037,7 +5084,34 @@ ${stillTime !== undefined ? `
|
|
|
5037
5084
|
message: 'the file is ' + __vd.output[0] + 'x' + __vd.output[1] + ', not the requested ' + __vd.requested[0] + 'x' + __vd.requested[1] + ': H.264 needs even dimensions, so an odd side was rounded up by 1 px (the extra row / column is background). For an exact-pixel spec, use an even canvas size, or crop the extra line when re-encoding (ffmpeg -vf crop=' + __vd.requested[0] + ':' + __vd.requested[1] + ':0:0).' }]);
|
|
5038
5085
|
if (result.fidelity.note) delete result.fidelity.note;
|
|
5039
5086
|
}
|
|
5040
|
-
//
|
|
5087
|
+
${deterministic ? ` // DETERMINISTIC: the container timestamps are pinned either way; the PIXELS
|
|
5088
|
+
// are only identical run to run on the software encoder, because the
|
|
5089
|
+
// browser may pick a different hardware backend each time (FxTool
|
|
5090
|
+
// e34ae03d: 92/255 between backends, 2/255 run to run on hardware). The
|
|
5091
|
+
// engine requests software and reports what it got.
|
|
5092
|
+
// Where the studio says outright whether the file repeats (bitExact, FxTool
|
|
5093
|
+
// PR #31: it test-encodes and keeps whichever encoder does — often the
|
|
5094
|
+
// HARDWARE one on a Mac, since Chrome's software H.264 is not repeatable at
|
|
5095
|
+
// some sizes), that answer decides; the encoder name is only the fallback
|
|
5096
|
+
// for studios that do not report it.
|
|
5097
|
+
if (result && result.success) {
|
|
5098
|
+
const __hw = __vr && __vr.hardwareAcceleration;
|
|
5099
|
+
const __exact = __vr && typeof __vr.bitExact === 'boolean' ? __vr.bitExact : null;
|
|
5100
|
+
if (__exact === false) {
|
|
5101
|
+
result.fidelity = result.fidelity || { warnings: [] };
|
|
5102
|
+
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'determinism_not_pinned',
|
|
5103
|
+
message: 'deterministic was asked for, but no encoder on this machine repeats byte for byte at this size (the studio test-encoded and checked): the pixels may differ slightly between runs. Do not cache this file by hash; a different frame size may repeat.' }]);
|
|
5104
|
+
if (result.fidelity.note) delete result.fidelity.note;
|
|
5105
|
+
} else if (__exact === null && __hw !== 'prefer-software') {
|
|
5106
|
+
result.fidelity = result.fidelity || { warnings: [] };
|
|
5107
|
+
result.fidelity.warnings = (result.fidelity.warnings || []).concat([{ code: 'determinism_not_pinned',
|
|
5108
|
+
message: __hw
|
|
5109
|
+
? 'deterministic was asked for, but the encoder ran with hardwareAcceleration ' + JSON.stringify(__hw) + ': the container is pinned, the pixels may still differ slightly between runs (hardware encoders vary). Do not cache this file by hash.'
|
|
5110
|
+
: 'deterministic pinned the container timestamps, but this studio does not report which encoder backend ran, so the pixels may differ slightly between runs if the browser switches between hardware and software encoding. Do not rely on byte-identical files from this studio.' }]);
|
|
5111
|
+
if (result.fidelity.note) delete result.fidelity.note;
|
|
5112
|
+
}
|
|
5113
|
+
}
|
|
5114
|
+
` : ''} // LUMA OUTSIDE 16-235 (8.35, FxTool 4f1ec3ae). The broadcast encode is
|
|
5041
5115
|
// decoded back and measured: H.264 ringing at hard edges puts a fraction of
|
|
5042
5116
|
// a percent of samples out of range, which QC rejects. broadcastHeadroom
|
|
5043
5117
|
// cuts it but never to zero, so a legaliser pass is the requirement.
|
|
@@ -7701,6 +7775,12 @@ ${loads}
|
|
|
7701
7775
|
// Anything else (asset id, data:, URL) passes through untouched.
|
|
7702
7776
|
// @engine-methods analyzeAudio animateToBeat
|
|
7703
7777
|
const resolveSource = ` let __src = ${JSON.stringify(input.source || '')};
|
|
7778
|
+
// A large source was staged beside the code (see the handler); read it back.
|
|
7779
|
+
if (__src.indexOf('__ppStage:') === 0) {
|
|
7780
|
+
const __staged = window.__ppStage && window.__ppStage[__src.slice('__ppStage:'.length)];
|
|
7781
|
+
if (!__staged) return { success: false, error: 'the staged audio did not reach the page — try again, or upload it with pinepaper_media upload_audio and pass the returned id.' };
|
|
7782
|
+
__src = __staged;
|
|
7783
|
+
}
|
|
7704
7784
|
const __A = (typeof window !== 'undefined') && window.PinePaperAgent;
|
|
7705
7785
|
const __m = (__src && __A && typeof __A.listMedia === 'function')
|
|
7706
7786
|
? __A.listMedia().find(function(x) { return x.id === __src || x.registryId === __src; })
|
|
@@ -7724,6 +7804,11 @@ ${resolveSource}
|
|
|
7724
7804
|
if (r && typeof r === 'object' && 'ok' in r) {
|
|
7725
7805
|
return { success: r.ok !== false, ...r };
|
|
7726
7806
|
}
|
|
7807
|
+
// Nothing back is not a result: it was reported as success with an empty
|
|
7808
|
+
// body (gap 5.5), which reads as "analysed, no beats".
|
|
7809
|
+
if (r === undefined || r === null) {
|
|
7810
|
+
return { success: false, error: '${method} returned nothing, so there is no result to report. If the source is large, upload it with pinepaper_media upload_audio and pass the returned id.' };
|
|
7811
|
+
}
|
|
7727
7812
|
return { success: true, result: r };
|
|
7728
7813
|
})();`.trim();
|
|
7729
7814
|
if (input.action === 'analyze') {
|
|
@@ -8247,7 +8332,12 @@ ${resolve}
|
|
|
8247
8332
|
// engine mints a NUMBER. Returning the number meant the id could not be
|
|
8248
8333
|
// round-tripped: pass it back to add/remove and zod rejects it before the
|
|
8249
8334
|
// call is made.
|
|
8250
|
-
|
|
8335
|
+
// THE REGISTRY ID, not Paper's. createPrecomp returns the group, whose
|
|
8336
|
+
// .id is Paper's numeric id ("49"), which no tool accepts back ("precomp:
|
|
8337
|
+
// no such item(s): 49", round 10, 10.11). The registry id — what
|
|
8338
|
+
// listPrecomps and every other tool use — is on the group's data.
|
|
8339
|
+
const _d = (precomp && precomp.data) || {};
|
|
8340
|
+
const _pid = _d.registryId || _d.id || (precomp && precomp.id !== undefined ? precomp.id : precomp);
|
|
8251
8341
|
return { success: true, action: 'create', precompId: String(_pid), itemCount: items.length, name: ${JSON.stringify(input.name || '')} };
|
|
8252
8342
|
})();`.trim();
|
|
8253
8343
|
}
|
|
@@ -8258,7 +8348,11 @@ ${resolve}
|
|
|
8258
8348
|
// addToPrecomp answers falsy when the precomp or the item is not found, and
|
|
8259
8349
|
// this reported success regardless — the same silent-success shape as an
|
|
8260
8350
|
// unknown itemId elsewhere.
|
|
8261
|
-
|
|
8351
|
+
// The engine takes the ITEM, not its id (it clones it); an id string made
|
|
8352
|
+
// add throw and could never have worked. Resolved here, and a miss named.
|
|
8353
|
+
const _item = app.getItemById(${JSON.stringify(input.itemId || '')});
|
|
8354
|
+
if (!_item) return { success: false, action: 'add', error: 'no item ' + ${JSON.stringify(JSON.stringify(input.itemId || ''))} + ' on the canvas — check the id with pinepaper_get_items.' };
|
|
8355
|
+
const _ok = app.addToPrecomp(${JSON.stringify(input.precompId || '')}, _item);
|
|
8262
8356
|
if (_ok === false || _ok === null || _ok === undefined) {
|
|
8263
8357
|
return { success: false, action: 'add', error: 'nothing was added — check the precompId (create returns it as a string) and the itemId with pinepaper_get_items.' };
|
|
8264
8358
|
}
|
|
@@ -8268,7 +8362,14 @@ ${resolve}
|
|
|
8268
8362
|
return `
|
|
8269
8363
|
// Remove item from precomp
|
|
8270
8364
|
(function() {
|
|
8271
|
-
|
|
8365
|
+
// The item, not its id (the engine checks item.parent); and its answer is
|
|
8366
|
+
// read — null meant nothing was removed, and this said success regardless.
|
|
8367
|
+
const _item = app.getItemById(${JSON.stringify(input.itemId || '')});
|
|
8368
|
+
if (!_item) return { success: false, action: 'remove', error: 'no item ' + ${JSON.stringify(JSON.stringify(input.itemId || ''))} + ' on the canvas — check the id with pinepaper_get_items.' };
|
|
8369
|
+
const _r = app.removeFromPrecomp(${JSON.stringify(input.precompId || '')}, _item);
|
|
8370
|
+
if (_r === null || _r === undefined || _r === false) {
|
|
8371
|
+
return { success: false, action: 'remove', error: 'nothing was removed — the item is not in that precomp, or the precompId is not a precomp (create returns it; list shows them).' };
|
|
8372
|
+
}
|
|
8272
8373
|
return { success: true, action: 'remove', precompId: ${JSON.stringify(input.precompId || '')}, itemId: ${JSON.stringify(input.itemId || '')} };
|
|
8273
8374
|
})();`.trim();
|
|
8274
8375
|
default:
|
|
@@ -9665,7 +9766,7 @@ ${checks.includes('contrast') ? ` const all = (app.itemRegistry && typeof app.i
|
|
|
9665
9766
|
// Upload video from URL
|
|
9666
9767
|
(async function() {
|
|
9667
9768
|
${guard}
|
|
9668
|
-
const info = await A.uploadVideo(${srcExpr(input.url, 'video')}, ${opts});
|
|
9769
|
+
const info = await A.uploadVideo(${srcExpr(input.url, 'video')}, ${input.atPlayhead ? `Object.assign(${opts}, { timeOffset: Number(app.playbackTime) || 0 })` : opts});
|
|
9669
9770
|
return { success: true, action: 'upload_video', media: info };
|
|
9670
9771
|
})();`.trim();
|
|
9671
9772
|
}
|
|
@@ -9680,7 +9781,7 @@ ${guard}
|
|
|
9680
9781
|
// Upload audio from URL
|
|
9681
9782
|
(async function() {
|
|
9682
9783
|
${guard}
|
|
9683
|
-
const info = await A.uploadAudio(${srcExpr(input.url, 'audio')}, ${opts});${input.volume !== undefined ? `
|
|
9784
|
+
const info = await A.uploadAudio(${srcExpr(input.url, 'audio')}, ${input.atPlayhead ? `Object.assign(${opts}, { timeOffset: Number(app.playbackTime) || 0 })` : opts});${input.volume !== undefined ? `
|
|
9684
9785
|
// THE LEVEL IS STORED UNDER ONE KEY AND EXPORTED FROM ANOTHER. The upload
|
|
9685
9786
|
// records volume as \`gain\` on the registry entry; the video exporter's mix
|
|
9686
9787
|
// reads \`audioGain\`. So a bed uploaded at 0.25 exported at unity (measured:
|
|
@@ -9729,10 +9830,37 @@ ${guard}
|
|
|
9729
9830
|
if (typeof A.setMediaClip !== 'function') { return { success: false, error: 'setMediaClip unavailable — update FxTool' }; }
|
|
9730
9831
|
${resolveMedia}
|
|
9731
9832
|
if (!__m) return __miss;
|
|
9732
|
-
|
|
9833
|
+
${input.timeOffset !== undefined ? ` // Moving the clip is the 4th argument, which older studios' setMediaClip
|
|
9834
|
+
// does not take (it dropped it silently): its declared arity is the tell.
|
|
9835
|
+
if (A.setMediaClip.length < 4) {
|
|
9836
|
+
return { success: false, action: 'set_clip', id: __mid, error: 'this studio can re-trim a clip but not move it (setMediaClip takes no start time here) — nothing was changed. Update the studio, or remove and re-upload the clip with timeOffset.' };
|
|
9837
|
+
}
|
|
9838
|
+
` : ''} const ok = A.setMediaClip(__mid, ${input.inPoint}, ${input.outPoint}${input.timeOffset !== undefined ? `, ${input.timeOffset}` : ''});
|
|
9733
9839
|
return ok
|
|
9734
|
-
? { success: true, action: 'set_clip', id: __mid, registryId: __m.registryId, inPoint: ${input.inPoint}, outPoint: ${input.outPoint} }
|
|
9840
|
+
? { success: true, action: 'set_clip', id: __mid, registryId: __m.registryId, inPoint: ${input.inPoint}, outPoint: ${input.outPoint}${input.timeOffset !== undefined ? `, timeOffset: ${input.timeOffset}` : ''} }
|
|
9735
9841
|
: { success: false, action: 'set_clip', id: __mid, error: 'the studio refused the clip window ${input.inPoint}–${input.outPoint} for ' + __mid + '.' };
|
|
9842
|
+
})();`.trim();
|
|
9843
|
+
// Split and transitions (FxTool, verified on main): the engine's own
|
|
9844
|
+
// refusals come back as {ok:false, reason} and are errors here.
|
|
9845
|
+
case 'split':
|
|
9846
|
+
return `
|
|
9847
|
+
// Split a media clip at canvas time ${input.at}
|
|
9848
|
+
(async function() {
|
|
9849
|
+
${guard}
|
|
9850
|
+
if (typeof A.splitMedia !== 'function') { return { success: false, action: 'split', error: 'this studio is too old to split a clip (no splitMedia) — nothing was changed.' }; }
|
|
9851
|
+
const r = await A.splitMedia(${JSON.stringify(input.id)}, ${input.at});
|
|
9852
|
+
if (!r || r.ok === false) return { success: false, action: 'split', error: (r && r.reason) || 'the studio refused the split' };
|
|
9853
|
+
return Object.assign({ success: true, action: 'split' }, r);
|
|
9854
|
+
})();`.trim();
|
|
9855
|
+
case 'add_transition':
|
|
9856
|
+
return `
|
|
9857
|
+
// ${input.transition?.type ?? 'crossfade'} between ${input.fromItemId} and ${input.toItemId}
|
|
9858
|
+
(function() {
|
|
9859
|
+
${guard}
|
|
9860
|
+
if (typeof A.addTransition !== 'function') { return { success: false, action: 'add_transition', error: 'this studio is too old for clip transitions (no addTransition) — nothing was changed.' }; }
|
|
9861
|
+
const r = A.addTransition(${JSON.stringify(input.fromItemId)}, ${JSON.stringify(input.toItemId)}, ${JSON.stringify(input.transition ?? {})});
|
|
9862
|
+
if (!r || r.ok === false) return { success: false, action: 'add_transition', error: (r && r.reason) || 'the studio refused the transition' };
|
|
9863
|
+
return Object.assign({ success: true, action: 'add_transition' }, r);
|
|
9736
9864
|
})();`.trim();
|
|
9737
9865
|
// ── Video-editing actions (v1.6.4) — these live on `app` (PinePaper
|
|
9738
9866
|
// facades), not the agent media API, so they carry their own guards.
|
|
@@ -9742,15 +9870,18 @@ ${resolveMedia}
|
|
|
9742
9870
|
return `
|
|
9743
9871
|
// Remap a clip's time — speed ramps, freeze frames, reverse (null clears)
|
|
9744
9872
|
(function() {
|
|
9873
|
+
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
9874
|
+
// registry id these app facades take (round 10, 10.12).
|
|
9875
|
+
const __reg = function(r) { const __A = window.PinePaperAgent; const __m = (__A && typeof __A.listMedia === 'function') ? __A.listMedia().find(function(x) { return x.id === r; }) : null; return (__m && __m.registryId) || r; };
|
|
9745
9876
|
if (typeof app.setTimeRemap !== 'function') { return { success: false, error: 'app.setTimeRemap unavailable — update FxTool to a time-remap-capable build' }; }
|
|
9746
|
-
const r = app.setTimeRemap(${S(input.id)}, ${S(input.remapTrack ?? null)});
|
|
9877
|
+
const r = app.setTimeRemap(__reg(${S(input.id)}), ${S(input.remapTrack ?? null)});
|
|
9747
9878
|
if (!(r && r.ok)) return { success: false, error: (r && r.error) || 'remap failed' };
|
|
9748
9879
|
// WHAT THE REMAP CANNOT DO, READ BACK FROM WHAT WAS STORED. The clip is on
|
|
9749
9880
|
// the timeline only for its trimmed window, and past that it is HIDDEN
|
|
9750
9881
|
// whatever the curve says — a remap to 6.5 s on a 5 s clip cut to black at
|
|
9751
9882
|
// 5.0 s. Source values outside [inPoint, outPoint] are clamped, so that part
|
|
9752
9883
|
// of the curve is a hold. Both answered success before.
|
|
9753
|
-
const __rid = typeof app._resolveId === 'function' ? app._resolveId(${S(input.id)}) : ${S(input.id)};
|
|
9884
|
+
const __rid = typeof app._resolveId === 'function' ? app._resolveId(__reg(${S(input.id)})) : ${S(input.id)};
|
|
9754
9885
|
const __e = __rid && app.itemRegistry && app.itemRegistry.get(__rid);
|
|
9755
9886
|
const __p = (__e && __e.properties) || {};
|
|
9756
9887
|
const __tr = Array.isArray(__p.timeRemap) ? __p.timeRemap : [];
|
|
@@ -9778,15 +9909,18 @@ ${resolveMedia}
|
|
|
9778
9909
|
return `
|
|
9779
9910
|
// Speed ramp: consecutive {duration, speed} segments compiled to a remap curve
|
|
9780
9911
|
(function() {
|
|
9912
|
+
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
9913
|
+
// registry id these app facades take (round 10, 10.12).
|
|
9914
|
+
const __reg = function(r) { const __A = window.PinePaperAgent; const __m = (__A && typeof __A.listMedia === 'function') ? __A.listMedia().find(function(x) { return x.id === r; }) : null; return (__m && __m.registryId) || r; };
|
|
9781
9915
|
if (typeof app.speedRamp !== 'function') { return { success: false, error: 'app.speedRamp unavailable — update FxTool to a time-remap-capable build' }; }
|
|
9782
|
-
const r = app.speedRamp(${S(input.id)}, ${S(input.segments)});
|
|
9916
|
+
const r = app.speedRamp(__reg(${S(input.id)}), ${S(input.segments)});
|
|
9783
9917
|
if (!(r && r.ok)) return { success: false, error: (r && r.error) || 'speed ramp failed' };
|
|
9784
9918
|
// WHAT THE REMAP CANNOT DO, READ BACK FROM WHAT WAS STORED. The clip is on
|
|
9785
9919
|
// the timeline only for its trimmed window, and past that it is HIDDEN
|
|
9786
9920
|
// whatever the curve says — a remap to 6.5 s on a 5 s clip cut to black at
|
|
9787
9921
|
// 5.0 s. Source values outside [inPoint, outPoint] are clamped, so that part
|
|
9788
9922
|
// of the curve is a hold. Both answered success before.
|
|
9789
|
-
const __rid = typeof app._resolveId === 'function' ? app._resolveId(${S(input.id)}) : ${S(input.id)};
|
|
9923
|
+
const __rid = typeof app._resolveId === 'function' ? app._resolveId(__reg(${S(input.id)})) : ${S(input.id)};
|
|
9790
9924
|
const __e = __rid && app.itemRegistry && app.itemRegistry.get(__rid);
|
|
9791
9925
|
const __p = (__e && __e.properties) || {};
|
|
9792
9926
|
const __tr = Array.isArray(__p.timeRemap) ? __p.timeRemap : [];
|
|
@@ -9822,8 +9956,11 @@ ${resolveMedia}
|
|
|
9822
9956
|
return `
|
|
9823
9957
|
// Match cut: align the SUBJECT across the cut (on-device detection)
|
|
9824
9958
|
(async function() {
|
|
9959
|
+
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
9960
|
+
// registry id these app facades take (round 10, 10.12).
|
|
9961
|
+
const __reg = function(r) { const __A = window.PinePaperAgent; const __m = (__A && typeof __A.listMedia === 'function') ? __A.listMedia().find(function(x) { return x.id === r; }) : null; return (__m && __m.registryId) || r; };
|
|
9825
9962
|
if (typeof app.matchCut !== 'function') { return { success: false, error: 'app.matchCut unavailable — update FxTool to a match-cut-capable build' }; }
|
|
9826
|
-
const r = await app.matchCut(${S(input.fromItemId)}, ${S(input.toItemId)}, ${opts});
|
|
9963
|
+
const r = await app.matchCut(__reg(${S(input.fromItemId)}), __reg(${S(input.toItemId)}), ${opts});
|
|
9827
9964
|
if (r && r.needsConsent) { return { success: false, needsConsent: true, cost: r.cost, error: 'Detection model download needs consent — re-call with consent: true' }; }
|
|
9828
9965
|
if (!r || !r.ok) { return { success: false, error: (r && r.error) || 'match cut failed' }; }
|
|
9829
9966
|
return { success: true, action: 'match_cut', aspectMismatch: r.aspectMismatch || false };
|
|
@@ -9841,9 +9978,12 @@ ${resolveMedia}
|
|
|
9841
9978
|
return `
|
|
9842
9979
|
// Track matte: the matte item's ${input.channel || 'luma'} drives the target's alpha${input.live ? ' (LIVE — re-cuts as the matte animates)' : ''}
|
|
9843
9980
|
(async function() {
|
|
9981
|
+
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
9982
|
+
// registry id these app facades take (round 10, 10.12).
|
|
9983
|
+
const __reg = function(r) { const __A = window.PinePaperAgent; const __m = (__A && typeof __A.listMedia === 'function') ? __A.listMedia().find(function(x) { return x.id === r; }) : null; return (__m && __m.registryId) || r; };
|
|
9844
9984
|
if (typeof app.applyTrackMatte !== 'function') { return { success: false, error: 'app.applyTrackMatte unavailable — update FxTool to a track-matte-capable build' }; }
|
|
9845
9985
|
try {
|
|
9846
|
-
await app.applyTrackMatte(${S(input.id)}, ${S(input.matteItemId)}, ${opts});
|
|
9986
|
+
await app.applyTrackMatte(__reg(${S(input.id)}), __reg(${S(input.matteItemId)}), ${opts});
|
|
9847
9987
|
return { success: true, action: 'apply_track_matte', id: ${S(input.id)}, matte: ${S(input.matteItemId)}, live: ${S(!!input.live)} };
|
|
9848
9988
|
} catch (e) {
|
|
9849
9989
|
// The facade throws on a missing raster/matte rather than returning {ok:false}.
|
|
@@ -9856,8 +9996,11 @@ ${resolveMedia}
|
|
|
9856
9996
|
return `
|
|
9857
9997
|
// Stop a live matte, leaving the last cut in place
|
|
9858
9998
|
(function() {
|
|
9999
|
+
// A media id (vraster_ / araster_, what upload returns) is resolved to the
|
|
10000
|
+
// registry id these app facades take (round 10, 10.12).
|
|
10001
|
+
const __reg = function(r) { const __A = window.PinePaperAgent; const __m = (__A && typeof __A.listMedia === 'function') ? __A.listMedia().find(function(x) { return x.id === r; }) : null; return (__m && __m.registryId) || r; };
|
|
9859
10002
|
if (typeof app.stopLiveMatte !== 'function') { return { success: false, error: 'app.stopLiveMatte unavailable — update FxTool' }; }
|
|
9860
|
-
const r = app.stopLiveMatte(${S(input.id)});
|
|
10003
|
+
const r = app.stopLiveMatte(__reg(${S(input.id)}));
|
|
9861
10004
|
return r && r.ok ? { success: true, action: 'stop_live_matte' } : { success: false, error: (r && r.error) || 'no live matte on that item' };
|
|
9862
10005
|
})();`.trim();
|
|
9863
10006
|
}
|
|
@@ -11876,4 +12019,119 @@ ${guard('unlockAllItems')}${pass('app.unlockAllItems()')}
|
|
|
11876
12019
|
}
|
|
11877
12020
|
// Export singleton instance
|
|
11878
12021
|
export const codeGenerator = new PinePaperCodeGenerator();
|
|
12022
|
+
/**
|
|
12023
|
+
* WORDS ARE VECTOR (cloud: every generated plate is text-free; the words come
|
|
12024
|
+
* back as textLayers). Lays them out as real PinePaper text over a placed
|
|
12025
|
+
* plate, by role, inside title-safe (5%), sized from the canvas height:
|
|
12026
|
+
* headline top-left (Anton by default, fitted to the safe width), subhead
|
|
12027
|
+
* under it, caption / cta stacked up from the bottom-left, label top-right.
|
|
12028
|
+
* Each text is checked against the plate's average colour under it (WCAG:
|
|
12029
|
+
* 4.5:1, 3:1 for large text) and gets the lightest dark scrim that passes
|
|
12030
|
+
* when it falls short. A body for pinepaper_execute_custom_code (async).
|
|
12031
|
+
*/
|
|
12032
|
+
export function emitTextLayersPlacement(layers, plateRef) {
|
|
12033
|
+
const ROLE_FONT = { headline: 'Anton' };
|
|
12034
|
+
const families = [...new Set(layers.map((l) => l.font || ROLE_FONT[l.role ?? ''] || 'Inter'))];
|
|
12035
|
+
const fontBlocks = families.map((f) => `{${emitEnsureFont(f)}
|
|
12036
|
+
if (__font) fonts.push(__font);
|
|
12037
|
+
}`).join('\n');
|
|
12038
|
+
return `
|
|
12039
|
+
const fonts = [];
|
|
12040
|
+
${fontBlocks}
|
|
12041
|
+
const plate = (function(r) {
|
|
12042
|
+
let it = app.getItemById(r);
|
|
12043
|
+
if (!it && window.PinePaperAgent && typeof window.PinePaperAgent.listMedia === 'function') {
|
|
12044
|
+
const m = window.PinePaperAgent.listMedia().find(function(x) { return x.id === r || x.registryId === r; });
|
|
12045
|
+
if (m) it = app.getItemById(m.registryId);
|
|
12046
|
+
}
|
|
12047
|
+
return it;
|
|
12048
|
+
})(${JSON.stringify(plateRef)});
|
|
12049
|
+
const cs = app.getCanvasSize();
|
|
12050
|
+
const W = cs.width, H = cs.height, mx = Math.round(W * 0.05), my = Math.round(H * 0.05);
|
|
12051
|
+
const LAYERS = ${JSON.stringify(layers)};
|
|
12052
|
+
const SPEC = {
|
|
12053
|
+
headline: { size: 0.075, font: 'Anton', where: 'top' },
|
|
12054
|
+
subhead: { size: 0.036, font: 'Inter', where: 'top' },
|
|
12055
|
+
caption: { size: 0.028, font: 'Inter', where: 'bottom' },
|
|
12056
|
+
cta: { size: 0.032, font: 'Inter', where: 'bottom', bold: true },
|
|
12057
|
+
label: { size: 0.022, font: 'Inter', where: 'topright' },
|
|
12058
|
+
};
|
|
12059
|
+
const lin = function(v) { return v <= 0.04045 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4); };
|
|
12060
|
+
const make = function(l, spec, x, y, anchor) {
|
|
12061
|
+
const size = Math.max(8, Math.round(H * spec.size));
|
|
12062
|
+
const it = app.create('text', { content: l.content, x: x, y: y, fontSize: size, fontFamily: l.font || spec.font, color: '#ffffff', anchor: anchor });
|
|
12063
|
+
if (!it) return null;
|
|
12064
|
+
if (spec.bold) it.fontWeight = 'bold';
|
|
12065
|
+
if (typeof app.fitText === 'function') {
|
|
12066
|
+
try { app.fitText(it, { maxWidth: W - 2 * mx, maxFontSize: size, minFontSize: Math.max(8, Math.round(size * 0.4)), hold: anchor.indexOf('bottom') === 0 ? 'bottom' : 'top' }); } catch (_) { /* keeps its size */ }
|
|
12067
|
+
}
|
|
12068
|
+
return it;
|
|
12069
|
+
};
|
|
12070
|
+
const contrast = function(it, spec) {
|
|
12071
|
+
if (!plate || typeof plate.getAverageColor !== 'function') return { checked: false, note: 'the plate is not an image the studio can sample (a video plate): contrast was not checked.' };
|
|
12072
|
+
// The BRIGHT end of what is under the text, not its mean: a caption across
|
|
12073
|
+
// bright reflections on a dark road averaged to 15:1 and was unreadable in
|
|
12074
|
+
// the bright part. An 8 x 3 grid of cells, each averaged, judged at the
|
|
12075
|
+
// 90th percentile of luminance.
|
|
12076
|
+
const b0 = it.bounds, lums = [];
|
|
12077
|
+
for (let gy = 0; gy < 3; gy++) {
|
|
12078
|
+
for (let gx = 0; gx < 8; gx++) {
|
|
12079
|
+
const cw = b0.width / 8, ch = b0.height / 3;
|
|
12080
|
+
const cc = plate.getAverageColor(new paper.Rectangle(b0.left + gx * cw, b0.top + gy * ch, cw, ch));
|
|
12081
|
+
if (cc) lums.push(0.2126 * lin(cc.red) + 0.7152 * lin(cc.green) + 0.0722 * lin(cc.blue));
|
|
12082
|
+
}
|
|
12083
|
+
}
|
|
12084
|
+
if (!lums.length) return { checked: false };
|
|
12085
|
+
lums.sort(function(x, y) { return x - y; });
|
|
12086
|
+
const L = lums[Math.floor(0.9 * (lums.length - 1))];
|
|
12087
|
+
const ratio = 1.05 / (L + 0.05);
|
|
12088
|
+
const large = it.fontSize >= 24 || (spec.bold && it.fontSize >= 18.66);
|
|
12089
|
+
const required = large ? 3 : 4.5;
|
|
12090
|
+
const out = { checked: true, ratio: Math.round(ratio * 100) / 100, required: required, approximate: 'against the bright end (90th percentile of 24 sampled cells) of the plate behind the text' };
|
|
12091
|
+
if (ratio >= required) return out;
|
|
12092
|
+
// The lightest black scrim that reaches the requirement: under it the
|
|
12093
|
+
// background luminance is L * (1 - a).
|
|
12094
|
+
const a = Math.min(0.85, Math.max(0.2, 1 - (1.05 / required - 0.05) / L));
|
|
12095
|
+
const pad = Math.round(it.fontSize * 0.35);
|
|
12096
|
+
const b = it.bounds;
|
|
12097
|
+
const s = app.create('rectangle', { x: b.left - pad, y: b.top - pad, width: b.width + 2 * pad, height: b.height + 2 * pad, color: '#000000', anchor: 'top-left' });
|
|
12098
|
+
if (s) { s.opacity = a; if (typeof s.insertBelow === 'function') s.insertBelow(it); }
|
|
12099
|
+
out.scrim = { itemId: s && s.data && s.data.registryId, opacity: Math.round(a * 100) / 100 };
|
|
12100
|
+
out.ratioAfter = Math.round(1.05 / (L * (1 - a) + 0.05) * 100) / 100;
|
|
12101
|
+
return out;
|
|
12102
|
+
};
|
|
12103
|
+
const role = function(l) { return SPEC[l.role] ? l.role : 'caption'; };
|
|
12104
|
+
const placed = [];
|
|
12105
|
+
let top = my;
|
|
12106
|
+
LAYERS.filter(function(l) { return SPEC[role(l)].where === 'top'; }).forEach(function(l) {
|
|
12107
|
+
const spec = SPEC[role(l)];
|
|
12108
|
+
const it = make(l, spec, mx, top, 'top-left');
|
|
12109
|
+
if (!it) return;
|
|
12110
|
+
top = it.bounds.bottom + Math.round(H * 0.015);
|
|
12111
|
+
placed.push({ it: it, l: l, spec: spec });
|
|
12112
|
+
});
|
|
12113
|
+
let bottom = H - my;
|
|
12114
|
+
LAYERS.filter(function(l) { return SPEC[role(l)].where === 'bottom'; }).slice().reverse().forEach(function(l) {
|
|
12115
|
+
const spec = SPEC[role(l)];
|
|
12116
|
+
const it = make(l, spec, mx, bottom, 'bottom-left');
|
|
12117
|
+
if (!it) return;
|
|
12118
|
+
bottom = it.bounds.top - Math.round(H * 0.012);
|
|
12119
|
+
placed.unshift({ it: it, l: l, spec: spec });
|
|
12120
|
+
});
|
|
12121
|
+
let rtop = my;
|
|
12122
|
+
LAYERS.filter(function(l) { return SPEC[role(l)].where === 'topright'; }).forEach(function(l) {
|
|
12123
|
+
const spec = SPEC[role(l)];
|
|
12124
|
+
const it = make(l, spec, W - mx, rtop, 'top-right');
|
|
12125
|
+
if (!it) return;
|
|
12126
|
+
rtop = it.bounds.bottom + Math.round(H * 0.01);
|
|
12127
|
+
placed.push({ it: it, l: l, spec: spec });
|
|
12128
|
+
});
|
|
12129
|
+
if (app.historyManager) app.historyManager.saveState();
|
|
12130
|
+
return {
|
|
12131
|
+
textItems: placed.map(function(p) {
|
|
12132
|
+
return { itemId: p.it.data && p.it.data.registryId, role: role(p.l), content: p.l.content, fontFamily: p.it.fontFamily, fontSize: Math.round(p.it.fontSize), contrast: contrast(p.it, p.spec) };
|
|
12133
|
+
}),
|
|
12134
|
+
fonts: fonts,
|
|
12135
|
+
};`;
|
|
12136
|
+
}
|
|
11879
12137
|
//# sourceMappingURL=code-generator.js.map
|