Version v1.85.0 Changelog October 27, 2026
Breaking Changes
Video and Audio Export
- Engine: A video export without a frame rate option now uses the scene’s
scene/frameRate, which defaults to 30. The export then matches the motion blur of the preview. An explicit frame rate still wins. Passframerate: 30to keep the previous output. - Engine (Swift + Android):
VideoExportOptions.framerate(Swift) andExportVideoOptions.frameRate(Android) are nowFloat?and default tonil(Swift) ornull(Android), which uses the scene’s frame rate. Code that reads the property needsoptions.framerate ?? 30(Swift) oroptions.frameRate ?: 30F(Android) to get the previous value. Passframerate: 30(Swift) orframeRate = 30F(Android) to keep the previous output. - Engine: A video export now fails unless its frame rate is greater than 0 and at most 240 frames per second. Before, a frame rate of 0, a negative frame rate or NaN gave an undefined frame count. A frame rate above 240 exported a very large number of frames. Pass a frame rate in that range, or omit it to use the scene’s frame rate.
- Engine: MP4 video exports now embed a PNG cover from the page playback position at export start. An omitted or null
coverTimeOffsetuses this default, clamped to the exported frames for partial exports. Set an explicit offset in seconds from the exported video start to override it. Times between frame boundaries select the earlier exported frame. Transparent areas use black, matching video playback. Covers preserve the video aspect ratio and fit within 512 × 512 pixels. - Engine: An
audio/mp4export of an audio block or a video block now applies the playback settings of the block. These are its volume, mute state, playback speed, looping and audio fades, the volume and mute state of its page, and the fade of a transition. Before, the export returned the audio of the source file unchanged. A block that changes none of these settings still exports a copy of the source audio.- The export of a block that changes one of them is now mixed and encoded as 48 kHz stereo audio, like the export of a page: AAC, or Opus in a browser that encodes Opus but not AAC, such as Firefox. It fails where the export of a page fails: with no audio encoder, for a block with no duration, and for a block on no page.
@cesdk/nodeencodes neither AAC nor Opus, so there the export of such a block fails. - To export the source audio unchanged, reset these settings before the export.
- The export of a block that changes one of them is now mixed and encoded as 48 kHz stereo audio, like the export of a page: AAC, or Opus in a browser that encodes Opus but not AAC, such as Firefox. It fails where the export of a page fails: with no audio encoder, for a block with no duration, and for a block on no page.
- Engine: An export now fails after 60 seconds when a block that it exports waits for a resource that makes no loading progress. Before, the export never ended. The error names the blocks. Set the new
exportPendingTimeoutSecondssetting to change the limit, or to 0 to wait without limit. A block that you set to pending withsetStatedoes not count toward this limit. Three steps report no progress or only whole percents while they run, so a slow one counts toward the limit. These are an asynchronous URI resolver, the demux of a video, and a video transcode on iOS. On Node, the engine updates at full speed while a block is pending. Remove the block or dispose the engine after this error. - Engine/Android: From Android 9, export results and archives of 1 MiB or more reach Kotlin without a copy into the Java heap. They are direct
ByteBuffers without a backing array, soarray()now throwsUnsupportedOperationException. Read them withget(ByteArray), or write them to a channel.
Animations and Captions
- Engine: Setting an in, loop or out animation on a block now removes that animation from any other block that used it. Before, the blocks shared the animation, and destroying the block that got it last, or that block’s page, silently removed it from the other blocks too. To animate more than one block the same way, create one animation for each block.
- Engine: Captions on one caption track now share their animations. Applying an animation, “None” or an animation setting to a caption from the
ly.img.animationslibrary, a caption style or the editor’s animation actions applies it to every caption on the track. A caption whoseappearance/animationscope is locked keeps its own animations, and a change made on a locked caption changes no caption. - Engine: A caption added to a caption track takes the animations of the captions already there, in place of any animations it had. A caption made longer takes its animation durations, and the timing of a scale loop, from a caption on the track that holds them in full.
- To animate one caption only, call
setInAnimation,setOutAnimationorsetLoopAnimationon it after adding it to the track.
- To animate one caption only, call
- Engine: An animation applied from the
ly.img.animationslibrary, a caption style, or theanimation.in,animation.outandanimation.loopactions now fits into the block’s length. When an in and an out animation would overlap, both get shorter in proportion. A loop keeps its duration. - Engine: Captions are no longer offered the typewriter, block swipe, spread and merge animations or the writing style, which they never rendered. Applying one of them to a caption from the library or the animation actions changes nothing, and a caption style skips it.
- Engine: Applying an animation or an animation setting from the
ly.img.animationslibrary to one block now returns an error when itsappearance/animationscope is locked. Applying an animation to the selection skips that block.
Text
- Engine (Web / Node / Node Native / Android): The text style setters and getters, such as
setTextColor, now fail for a range past the end of the text. Before, Web and Node used the whole text, or the text up to its end, and Android used the text up to its end. Pass a range within the text, or -1 for the whole text. - Engine (Web / Node / Android):
setTextCursorRangenow reads a start index past the end of the text as the end of the text. Before, it read it as the start, so a range past the end on both sides selected the whole text. To select the whole text, pass a range from 0 to the length of the text. - Engine: The text range APIs, such as
getTextColors,setTextColor,getTextRunsandgetTextCursorRange, count a text variable such as{{name}}by its characters, as intext/text. Before, a variable counted as one character, so each index after it pointed to the wrong character, and a range over the whole text failed. A range that starts or ends inside a variable covers the whole variable. Code that counted a variable as one character must now count the characters oftext/text, as the range parameters on Web, Android and iOS already describe.
Layout
- Engine:
setWidth,setHeightandsetSizewithmaintainCropnow read the value in the block’s size mode, and a Percent size, left or top stays Percent. Before, they read a Percent value as design units, so the block shrank to less than one unit and became Absolute. If you pass a frame size in design units for a block with a Percent size, convert it first. Multiply it bygetWidth()and divide it bygetFrameWidth(), or use the height getters.
Resource Loading
- Engine/Node Native:
CreativeEngine.initnow rejects with an error when the engine cannot parsebaseURL, for example a URL with[in its path. In 1.84.0 the engine started with such a value and loaded none of its bundled files. To migrate, percent-encode the character, for example%5Bfor[. A non-ASCII letter in a URL needs no change:initnow encodes it, as@cesdk/nodedoes. - Engine (Web / Node): In the browser and in
@cesdk/node, asetURIResolverfunction that throws or returns no string now logs a warning, and the engine loads the URI unchanged, as@cesdk/node-nativealready does. Before, that image never finished loading in the browser, and the error ended the process in@cesdk/node. To keep a URI from loading, return a URI that cannot load, such as one with an unknown scheme. The block then gets anErrorstate.
API Types
- Engine/Swift:
BlockState.errorcarries theEngineErrorbehind the failure asengineErrorwhen the engine knows it. The case is nowerror(BlockStateError, engineError: EngineError?). Update each pattern match:case .error(.videoDecoding)becomescase .error(.videoDecoding, _), andcase let .error(kind)becomescase let .error(kind, _). Creating.error(.unknown)still works.==compares only the error kind, as on Android, so== .error(.videoDecoding)still works. To compare the engine error, match the case with a pattern, such ascase let .error(kind, engineError). - Engine/Android:
CMYKProfileInfohas two new properties,nameanduri. The engine returns this type and never takes it, so only code that builds one itself, in a test or a mock, needs a change: pass a value for each new property.
Deprecations
None in this release.
New Features
Archives and Scene Loading
- Engine (Web / Node / Android / Swift):
scene.loadandscene.loadFromArchiveURLaccept an options object with anonProgresscallback (Web, Node). It reports the percentage of an archive that the engine has read, from 0 to 100. A large archive in OPFS reports each percent, and an archive in memory reports only 100. On Node,@cesdk/node-nativereports the progress of an archive, and@cesdk/nodegives no call. On Android,scene.loadandscene.loadArchivetake anonProgresscallback for the progress of an archive. On Swift,scene.load(from:)with a URL andscene.loadArchive(from:)take anonProgressclosure for the progress of an archive. On Android and Swift, it gets calls only with the settingfeatures/fileSystemUsageEnabledon. - Engine: A scene or block archive now contains
preview.png, an image of its first page at the playback time when the save starts. The image fits within 512 × 512 pixels and keeps the aspect ratio of the page, and a smaller page keeps its size. An archive without a page, or with a page that the engine cannot draw, has nopreview.png. - Editor/Web: Importing a large scene or archive now shows a loading dialog with the progress of the import. A failed import shows an error dialog.
Streamed PDF Export
- Engine/Android: Added a
BlockApi.exportoverload that writes a PDF into anOutputStreamwhile it is encoded. A large multi-page document is then never held in memory as a whole. A slow destination slows the encoder down instead of letting the chunks queue up in memory. - Engine/Android: Added a
BlockApi.exportoverload that returns a PDF as a coldFlow<ByteBuffer>of chunks. The encoder waits until the collector takes each chunk, and the export stops when the collector leaves. - Engine/Android: Added
ExportOptions.pdfChunkSize, the upper bound in bytes for one chunk of a streamed PDF export.nulland a value below 1 use 512 KiB. Any other value is clamped to 4 KiB to 64 MiB.
Opus Audio
- Engine/Web: Video export and
audio/mp4export now encode the audio as Opus in a browser that encodes Opus but not AAC, such as Firefox or Chrome on Linux. Before, these exports failed. - Engine (Web / Node): The new
audioCodecexport option (AudioCodec.Auto,AudioCodec.AACorAudioCodec.Opus) selects the audio codec.Autois the default and keeps AAC where the browser encodes AAC. On Node, the option is available, but an export that has to encode Opus fails, because no Node.js engine has an Opus encoder. - Engine/Web: An MP4 with Opus audio now loads, so an export with Opus audio can be imported again.
- Engine/Web: The
video.encode.checkSupportaction now answerstruein a browser that encodes H.264 and Opus.
Scene Timeline and Composition Groups
- Engine: Added the scene property
scene/frameRate(experimental) in frames per second. It defaults to 30 and is saved with the scene. It accepts values greater than 0 and up to 240; other values fail with an error. - Engine (Web / Swift): Added
scene.addMarker,scene.getMarkersandscene.removeMarker(experimental) to add named points or spans, in seconds, to the scene timeline. Swift:scene.addMarker(name:time:duration:),scene.getMarkers()andscene.removeMarker(at:). - Engine: Added composition groups (experimental). When
group/compositionis true, the children of the group run on the group’s own clock. - Engine:
playback/speedis now available on groups (experimental) and scales the clock of a composition group’s children. - Engine: Added
playback/timeRemapandplayback/timeRemapEnabledon groups (experimental). When enabled, the time remap in seconds replaces the clock of a composition group’s children, and it accepts keyframes. - Engine: Audio that a composition group plays faster than 3x is muted, as a video above 3x is.
- Engine: Ungrouping a composition group keeps the playback speed of its members. A video in a group at speed 2 plays at speed 2 after ungrouping. A speed stays between 0.25 and 3 for an audio block and at least 0.25 for other members. A member that reaches a limit lasts longer or shorter, so it plays the same media.
Keyframes and Transforms
- Engine: Added
getVec2andsetVec2and the property typeVec2(experimental). The block propertypositionand the shape propertyshape/positionareVec2properties, so one call reads or writes both coordinates. A write that fails changes neither coordinate. - Engine: Added
setKeyframes,getKeyframes,getKeyframeMode,removeKeyframes,getAnimatedPropertiesandisPropertyAnimatable(experimental) to store keyframes on animatable block properties. Keyframe times go from 0 to 1 over the block’s duration, and each keyframe has a preset, bezier or hold easing. Scenes save the keyframes, and undo restores them. Scaling a block or changing the design unit also converts the keyframes of its sizes and positions. - Engine: Added keyframes (experimental) on the vec2 properties
position(a block) andshape/position(a shape), withspatialOutandspatialIntangents for curved motion paths.position/xandposition/ystay as they are. - Engine: Added the graphic, text and caption block properties
transform/scale/xandtransform/scale/y(experimental). They scale the block about its anchor without changing its frame size, a negative value flips it, and both take keyframes. A flip also flips the keyframes of the axis it flips. A scale keyframe whose sign differs from the static scale needs thelayer/flipscope. - Engine: Added the graphic, text and caption block properties
transform/anchor/xandtransform/anchor/y(experimental). They set the point that rotation, skew and scale pivot on. Addedtransform/skewandtransform/skewAxis(experimental, in degrees) to shear a block. Skew is limited to ±85°, like in After Effects. Without an anchor, skew and scale pivot on the top-left corner, and arotationkeyframe track turns the block around its center. - Engine: Added
hasTransformAnchor(experimental). It tells whether a graphic, text or caption block has an anchor, even one set to 0. - Engine: Added the graphic, text and caption block property
transform/autoOrient(experimental). When it is on, the block turns to follow the direction of its position keyframes, in addition to its rotation. It is off by default. - Engine: Added
setTransformParentandgetTransformParent(experimental), so a graphic or text block can follow the position, rotation and scale of another block. It does not take the opacity or layer order of that block. A hidden block can serve as the parent. The global bounding box of such a block follows its transform parent at the current playback time. - Engine (Web / Node / Node Native):
setKeyframesrefuses a keyframe whose value, easing or tangents it cannot read. The error names the keyframe and the field. - Engine: A block that its keyframed size moves into view draws, also when its page is out of view. A gradient fill whose keyframes give it more than one color is dithered like a static one, so it shows no bands.
- Engine:
setKeyframesrejects a number that names no value of an enum property. A keyframed value below the minimum of its property stops at the minimum. - Engine: Rotating or flipping a block turns or mirrors the keyframes of its position, rotation and skew, so its animation turns or mirrors with it. The keyframes of a group turn and mirror with the group. A turn other than a half turn joins
position/xandposition/ytracks into onepositiontrack. The two tracks must share the mode, the times and the easings. Other axis tracks only move with the block under such a turn. - Engine: Loading a scene drops each keyframe track that its block cannot play, such as a track of a property the block does not have. A keyframe with an unknown easing preset loads with linear easing. On the web and Android, each dropped track logs a warning.
- Engine:
setKeyframesandremoveKeyframesneed theappearance/animationscope and the scope that the setter of the property needs, such aslayer/moveforposition. Atransform/scale/xortransform/scale/ykeyframe that flips the block also needs thelayer/flipscope. - Engine: A
rotationkeyframe track turns a graphic or text block without an anchor around its center, assetRotationdoes. Settransform/anchor/xortransform/anchor/y, even to 0, to turn it around the anchor. - Engine: Aligning, distributing and cutting out a block that follows a transform parent use its place on the page.
fillParentends the link. A copy saved without its transform parent keeps its place on the page. Its position, rotation and scale keyframes keep playing in place when the transform parent is not animated and has no mirror, shear or unequal scale. The copy does not follow the transform parent again. Moving a group moves a follower of a parent with a zero scale along the axis that the parent keeps. A group member with a zerotransform/scale/xortransform/scale/yscales, turns and flips with its group, and keeps a finite position. - Engine: Loading a scene keeps only the values that the API accepts for the properties added in this release. A matte whose source is missing, on another page or in a matte cycle is dropped. Anchor, skew, transform parent and motion blur load only on graphic and text blocks. A value that is not finite, a stroke miter limit below 1 and a composition speed below 0.25 load as their defaults.
Motion Blur
- Engine: Added motion blur (experimental) for video export. Turn it on with the graphic, text and caption block property
motionBlur/enabled. A block blurs when keyframes or animations move it, or when its video fill plays a Lottie animation. Image export is not blurred. - Engine: Three scene properties (experimental) tune the blur.
motionBlur/shutterAngletakes 0 to 720 degrees, default 180.motionBlur/shutterPhasetakes -360 to 360 degrees, default 0.motionBlur/samplestakes 1 to 64, default 16. A value outside its range fails. - Engine: The preview draws the same blur. While it plays or changes, it draws smaller blur images and fewer samples to keep up. It draws at least 4 samples, or all of them when the scene sets fewer. On a display that refreshes slower than the scene frame rate, it keeps up at the refresh rate of the display. It draws all samples once it settles. Set the experimental
features/previewMotionBlurEnabledsetting tofalseto turn the preview blur off. - Engine: The preview motion blur draws also when an effect of the blurred block is turned off. It leaves out a block in crop or text editing, so the edit shows. A redraw request does not make it draw a frame with fewer samples.
Effects and Blur
- Engine: Added the
blur/uniform/modeproperty (experimental). In the newBlockmode, a uniform blur blurs the whole block, including its outline, and the blur spreads past the block. Groups now support a uniform blur, which blurs their children together in either mode. On a page, theBlockmode blurs the page and its content together, cut at the page edge. Linear, radial and mirrored blurs are rejected on a group.Fillstays the default and blurs the fill inside the block’s shape, as before. - Engine: Added the
tint,fill,tritone,levels,invert,thresholdandshift_channelseffects (experimental). Scenes that use them do not open in earlier versions. - Engine: Added the
corner_pin,bulge,wave_warpanddisplacement_mapeffects (experimental). They distort a block like the After Effects effects of the same names. A corner pin whose four corners do not form a valid quad draws nothing. Scenes that use them do not open in earlier versions. - Engine (Web / Swift / Android): Added
engine.block.setEffectSource(effect, source)andengine.block.getEffectSource(effect)(experimental) to set the block that a displacement map effect reads as its map. Swift:setEffectSource(effectID:source:)andgetEffectSource(effectID:). Android:setEffectSource(effectBlock, source)andgetEffectSource(effectBlock). - Engine: With transparent click-through on, the clickable area of an image with a displacement map follows each edit of the map source block. An image whose wipe or other effect has keyframes is clickable wherever the image itself is opaque.
- Engine: A displacement map leaves out the blocks of its source that are outside their time span at the playhead.
- Engine: A
corner_pineffect whose four corners do not form a valid quad draws nothing, also for a single frame of an animation. - Engine: Added the
linear_wipe,radial_wipeandvenetian_blindseffects (experimental). They hide parts of a block and match the After Effects wipes of the same names. The stripes of Venetian Blinds are at least 1 pixel wide, with edges at least 0.5 pixels soft, in every design unit. Scenes that use them do not open in earlier versions. - Engine: Added the
strokeeffect (experimental), which draws a brush stroke along one or all of a block’s masks, over the fill or on its own. Scenes that use it do not open in earlier versions.
Masks and Track Mattes
- Engine: Added masks (experimental) for graphic, text and group blocks. Create a mask with
createMask, give it a shape withsetShape, and attach it withappendMaskorinsertMask. A block shows only the area its masks cover. Each mask has the propertiesmask/mode(Add, Subtract, Intersect, Lighten, Darken, Difference or None),mask/opacity,mask/feather,mask/expansionandmask/inverted. SVG export ignores the mask opacity and feather. Scenes with masks do not open in earlier versions. - Engine: Added
setMatteandgetMatte(experimental) to mask a graphic, text or group block with the alpha or luminance of another block, like a track matte. The matte source block is not drawn on its own, and SVG export does not apply mattes, so a matted block exports whole. If the source is hidden or outside its time range, an alpha or luma matte hides the block. - Engine:
setMatteneeds thelayer/clippingscope of the masked block and thelayer/visibilityscope of a new source block, because a matte source stops drawing on its own. A new mode for the same source needs nolayer/visibilityscope. - Engine:
insertChildandappendChildfail when a move would put a matted block inside its matte source, or on another page than its source. A block outside the scene, such as a loaded block, counts only when it joins the scene, so loaded blocks can join in any order. A drag onto another page leaves a matted block on the page of its source. Move a matted block and its source together, for example as one group. - Engine: A click on an area that the masks of a block hide selects the block below first, as a click on a transparent pixel of an image does.
- Engine: Track mattes and block blur draw the whole drop shadow of a rotated block. They also keep the blur of a blur animation, and a shadow that a block blur spreads.
Shapes and Shape Modifiers
- Engine: Added the compound shape
//ly.img.ubq/shape/compound(experimental). It combines shape blocks, its subshapes, into one outline withshape/compound/mode(Merge,Add,Subtract,Intersect,Exclude), and the result stays editable. Manage the subshapes withgetSubshapes,insertSubshape,appendSubshapeandremoveSubshape. Place each subshape withshape/position/x,shape/position/y,shape/width,shape/height,shape/rotation,shape/skewandshape/skewAxis. These take effect only while the shape is a subshape. Scenes that use it do not open in earlier versions. - Engine: A shape tree nests at most 128 shapes, from its top shape to its deepest subshape.
insertSubshapeandappendSubshapereturn an error for a deeper subshape, and loading a scene drops the deeper subshapes. - Engine: The
Vectoredit mode does not open on a compound shape. It opens on a shape with modifiers or keyframes only when that shape is a vector path, so the edit keeps the modifiers and keyframes. - Engine: Added shape modifiers (experimental).
createModifier,supportsModifiers,getModifiers,appendModifier,insertModifierandremoveModifiermanage an ordered list of modifiers on a shape. The modifiers change the shape’s path before its fill and stroke are drawn. The first modifier istrim, which keeps only a part of the path (modifier/trim/start,end,offsetandmode).findByTypefinds the modifiers of a type (Android:findByType(ModifierType), Swift:find(byType: ModifierType)). Scenes that use them do not open in earlier versions. - Engine: Added the shape modifiers
round_corners,offset,zigzag,pucker_bloatandtwist(experimental). All their properties can be animated with keyframes. Scenes that use them do not open in earlier versions. - Engine:
round_cornersrounds the corners of a shape.offsetgrows or shrinks it.zigzagadds peaks and valleys to its edges.pucker_bloatpulls its corners towards its center or pushes them away.twisttwists it around a center. - Engine: Added the
repeatershape modifier (experimental). It drawsmodifier/repeater/copiescopies of a shape, from 0 to 1024. Each copy moves one step further bypositionXandpositionY. It also scales byscaleXandscaleYand turns byrotation, aboutanchorXandanchorY.offsetshifts every copy by whole steps, andcompositedraws each copy below or above the one before. The opacity goes fromstartOpacitytowardsendOpacity. Every repeater property can be animated with keyframes. On a subshape of a compound shape or on the shape of a mask, a repeater repeats the outline, without the opacity fade. Scenes that use it do not open in earlier versions. - Engine: A
repeatermodifier on a subshape of a compound shape or on the shape of a mask repeats that outline. Combining blocks and clicking a block also use every copy of a repeater. - Engine: An effect, mask or modifier that several blocks share changes its lengths once in one edit. This holds for a design unit change, a group scale, a resize of several blocks and a page crop.
- Engine: Added the star shape properties
shape/star/outerRadius,innerRadius,outerRoundness,innerRoundnessandrotation(experimental).shape/star/innerDiameterstill works and now reports the ratio of the inner to the outer radius. - Engine: Added
fill/gradient/radial/focalXandfocalY(experimental) to move the start of a radial gradient away from its center. - Engine: Added the
stroke/miterLimitproperty (experimental). Graphic blocks default to 35 and text blocks to 4, as they rendered before. Hit testing of stroked text now also uses 4 instead of 35. - Engine: Added
setStrokeFillandgetStrokeFill(experimental) to paint the stroke of a graphic block or page with a color, gradient or stripe fill.
Text
- Engine: The setting
features/fontLineGapEnabledis public. It defaults totrue, so the line height of text includes the line gap that a font declares. Set it tofalsefor the line heights of engine versions before 1.73. - Engine (Web / Swift): Added
block.setTextBaselineShiftandblock.getTextBaselineShifts(experimental) to move a text range up or down from the baseline, in design units. Swift:block.setTextBaselineShift(_:baselineShift:in:)andblock.getTextBaselineShifts(_:in:). - Engine: Added text animators (experimental).
createTextAnimator,appendTextAnimator,insertTextAnimator,removeTextAnimator,getTextAnimatorsandsupportsTextAnimatorsmanage them on text blocks. A text animator selects a range of characters, words or lines and changes their position, scale, rotation, skew, opacity, colors, stroke width, spacing and blur. Every text animator property can have keyframes. Text on a path does not show text animators yet. Scenes with text animators do not open in earlier versions. - Engine: Text animator tracking spaces a right-to-left line of two or more runs evenly.
- Engine: A combining mark, such as an accent or a Hebrew vowel point, moves, turns and scales with its letter under a text animator.
- Engine: Under a text animator, a text background stays behind every letter.
- Engine: Under a text animator, the dots and dashes of a dotted or dashed text stroke follow the animated stroke width.
- Engine: With a text animator and a text animation in the
Linewriting style, a stroked underline shows no seam at each letter. - Engine: A text animator blur adds to the blur of a Blur animation.
- Engine: Spot color text with a text animator and a Fade animation by character, word or line fades as it does without the text animator.
- Engine: The
text/textproperty accepts keyframes (experimental), so the text content can change over time. - Editor/Web: The horizontal alignment controls for text and captions have a new “Justify” option after “Right”. The
ly.img.text.alignmentfeature key shows it with the other alignments. Text on a path does not offer Justify, because the path holds one line.
Variables
- Engine: Added color, number, image and typeface variables (experimental) next to text variables.
variable.findAllstill lists only text variables. - Engine: A block property binds to a variable with
setVariableBinding(experimental) and then follows every change of the variable.getVariableBinding,removeVariableBindingandfindAllVariableBindingsread and remove the bindings. Image variables bind to image and video fill URIs. Scenes and archives save the variables and the bindings. - Engine (Web / Node / Node Native / Android / Swift): Added
variable.setColor,getColor,setNumber,getNumber,setImage,getImage,setTypeface,getTypeface,getTypeandfindAllOfType(experimental). Swift:variable.set(key:color:),set(key:number:),set(key:imageURL:),set(key:typeface:),getColor(key:),getNumber(key:),getImageURL(key:),getTypeface(key:),getType(key:)andfindAll(type:). - Engine: A bound property (experimental) changes only through its variable. A direct write to it returns an error until the binding is removed. Moving, resizing, rotating, cropping, aligning and scaling leave it unchanged. Changing the design unit converts a bound length. A number variable converts with it when every bound property holds its value.
- Engine: Changing the design unit also converts a number variable when every property bound to it holds its value. The variable then still reads what its bound lengths hold.
- Engine: Scaling or resizing a block returns an error when a bound variable sets a length of its shape, effects, masks, modifiers or text animators. A variable that moves or rotates a group returns an error when a member property that the change writes is bound.
- Engine: Typeface variables (experimental) hold a typeface. A text block whose typeface is bound to one uses it for the whole text and follows every change. A text color or font size bound to a variable also applies to the whole text.
- Engine: Added
variable.rename(Swift:variable.rename(key:to:), experimental) to rename a variable. Bindings and text templates follow the new name. The new name must not be empty, start or end with whitespace, or contain{{or}}. - Engine: Renaming a variable also renames it in the text keyframes of text and caption blocks.
Lottie
- Engine: Lottie video fills play
.lottiefiles (experimental). When a.lottiefile cannot be read,forceLoadAVResourcefails with an error that says why, for example an entry outside the archive. - Engine: Lottie video fills now show images and fonts that the Lottie file references by path or URL (experimental). Relative paths resolve against the location of the Lottie file. The engine loads only
http,httpsandbundleURIs, and files inside the folder of the Lottie file. It loads at most 256 references per Lottie file. - Engine: Lottie video fills now show the images embedded in the Lottie file.
- Engine: Lottie video fills now follow the block’s opacity and blend mode.
- Engine:
scene.saveToArchiveandblock.saveToArchiveput the images and fonts that a Lottie file references into the archive. - Engine: The images of a Lottie video fill load at most at the
maxImageSizesetting, as image fills do. - Engine: A Lottie video fill shows every image and font it references, also when they arrive at different times. An export waits for them.
- Engine: A Lottie video fill now draws the values of the animation’s slots. Before, it drew the default value of each property. Like the Lottie importer, it still ignores the themes of a
.lottiearchive. An image slot draws only when it embeds its image. The fill ignores the slots of an animation larger than 32 MiB, with the images of a.lottiearchive included.
Block State Errors
- Engine (Web / Node / Node Native / Android): An
Errorstate fromblock.getStatecarries the error behind it inengineErrorwhen the engine knows it (Web, Node, Node Native). On Android,BlockState.ErrorhasengineError, theEngineExceptionbehind the error, when the engine knows it. On Web, it has the same fields as a thrown engine error. A.lottiefile that cannot be read says why, for example an entry outside the archive. A video that cannot be decoded gives the decoder’s error.
Color and Images
- Engine:
getCMYKProfileInfonow also reports the profile’sname, from its ICC description, and theurithe document names it by. Pass thaturitosetCMYKProfileto put the same profile on another document in the same engine. A save and load rewrites theuri, and a profile that has not finished loading reports noname. - Engine (Web / Node):
unstable_setImportedImageDefinition()now acceptscolorKeyMaskand a singlemaskwithStencilorSoftkind for imported JPEG fills. Raster and PDF exports and click-through keep the transparency they describe. Masked and color-keyed base JPEGs support at most 67,108,864 pixels. Larger images fail to decode.
Platform Support
- Engine/Node Native: On Windows, the native engine now renders on the GPU through Direct3D 12, as macOS does through Metal and Linux through EGL. Before, it rendered on the CPU, and
device: 'gpu'gave an error. On a machine without a GPU,device: 'auto'renders on the CPU. The compiled GPU shaders stay in the temporary folder, so only the first process on a machine compiles them. - Engine/Swift: Add
editor.setSafeAreaInsetsandeditor.getSafeAreaInsets, which keep content clear of UI that covers the edges of the canvas in every zoom and clamp, as on Web.
Image Swap
- Editor (Android / iOS / RN / Flutter): Users can now swap two images. Press and hold an image, then drag it onto another image. The
dragToSwapFills/enabledengine setting turns it on, and the Design, Photo, Video, Postcard and Apparel Starter Kits have it on. - Engine (Android / Swift): On Android and iOS, a preview of the held image now follows the finger during the swap gesture. The device gives haptic feedback when the image lifts.
- Engine: The image swap gesture now passes through blocks whose
editor/selectscope is denied, as a tap does. An image with that scope denied can no longer be swapped.
Separate Audio
- Editor (Android + iOS): The new inspector bar button
InspectorBar.Button.rememberSeparateAudio()(Android) orInspectorBar.Buttons.separateAudio(...)(iOS) moves the audio of a video clip into new audio clips. Each audio track gets its own clip, and the video clip is muted. The audio clips start with the clip, keep its speed and volume, and take its name, or “Audio” when the clip has none. The button shows for background clips and overlay clips whose video has audio. The video editor starter kit shows it between “Shape” and “Layer”. - Editor (Android + iOS): The new editor event
EditorEvent.Selection.SeparateAudio(Android) orseparateAudioForSelection(iOS) does the same for the selected clip. - Editor (Android + iOS): Audio clips that play from a buffer, such as audio extracted with
createAudiosFromVideo, can now be trimmed in the timeline.
Editor Timeline and Animations
- Editor/Web: A group clip in the full video timeline has an expand button. It opens the group to one row per child, so a child can be moved, trimmed, split and reordered without ungrouping. The group fits its children after each edit. Selecting a block inside a closed group on the canvas opens the group and scrolls to the clip. The simplified timeline shows no expand button.
- Editor/Web: The
ly.img.video.timeline.clip.expandfeature key turns on the expand button, and the video editor configurations and video starter kits have it on. - Editor (Web / Android / iOS): The Animations section is available for captions in the video editor (Web), and the Animations button is available for captions (Android, iOS). On Android and iOS, it hides when the selected block’s
appearance/animationscope is locked.
Editor
- Editor/Android: Added a zoom transition between the canvas and page overview.
- Editor/Web: The Photobook Editor starter kit now offers an A4 book (21 × 29.7 cm) next to the portrait and square books. A4 has its own layouts and its own style scenes for the playful and chic styles.
- Editor/Web:
@cesdk/cesdk-jsexports the new@cesdk/enginetypesKeyframe,KeyframeValue,KeyframeEasing,KeyframePathVertex,TrackMode,MatteMode,BlockMatte,SceneMarker,VariableType,ModifierTypeandBlockStateEngineError.
Improvements & Behavior Changes
Performance and Memory
- Engine: A long MP4 or MOV video, such as a video of several hours, opens faster. On the web, the engine reads its index in a few large reads instead of one read per byte. On macOS, reading the 7.1 MB index of a 3.4 GB video now takes 6 reads and 12 ms instead of about 6.9 million reads and 1.9 s.
- Engine: With a
setURIResolverresolver (setUriResolveron Android), the engine draws the scene once for all images that start loading together, not once per image. On macOS with Metal, the first update of a 200-page photo book that shows every page now draws 2 times instead of 402 times, and takes 10.4 s instead of 18.1 s. - Engine: A JPEG photo that the engine needs at half its size or less now decodes at a reduced size, not at its full size first. This saves time and memory, for example on a zoomed-out canvas or for a photo at least twice
maxImageSize. On macOS, a 24-megapixel photo that the engine needs at 1024 pixels decodes in 74 ms instead of 90 ms, with a peak of 15 MB instead of 101 MB. Other photos, and PNG, WebP and other formats, still decode at their full size first. - Engine: Images on the canvas load and sharpen in the background, so a scroll or a zoom over many pages no longer waits for image decodes. In Chrome, a photo book with 173 pages and 946 photos scrolls at 60 frames per second, the slowest 5% of its frames take 22 ms, and the engine uses 172 MB after the scroll.
- Engine (Web / Node): The engine can now use up to 4 GB of memory in a desktop browser, also on Windows, and in
@cesdk/node. Before, the browser engine stopped at 2 GB, or at 1 GB on Windows, so a large scene crashed the engine or failed to export, and@cesdk/nodestopped at 2 GB, although its memory checks counted on 4 GB. Phones and tablets keep 2 GB, and so do Chrome and Edge on a device with less than 8 GB of RAM. - Engine/Node Native: On Windows with a GPU, video frames go to the GPU as they come from the decoder, and the GPU converts their colours.
Temporary Files
- Engine: On Android, iOS, macOS, Linux, Windows and Node Native, more large data stays in temporary files instead of memory, as the lines below list. Memory then stays flat across repeated imports and exports. Disk use grows until the app releases the results.
- Engine: Imports and exports now use free disk space for their temporary files.
- Engine: Loading a scene archive keeps each file of 1 MiB or more from the archive in a temporary file.
- Engine: Saving to an archive stages the archive in a temporary file. Android and iOS no longer leave a temporary file behind after each save.
- Engine: A remote file downloads into a temporary file on iOS, macOS and Linux, Node Native included, instead of into memory.
- Engine: An audio export stages its mix and its file in a temporary file, so a long export no longer holds the whole audio in memory.
- Engine: A buffer from
createBuffermoves to a temporary file once it holds 1 MiB. - Engine: A full disk during an export now fails the export with an error, where it could end the app on Android and Linux.
- Engine: On Windows, a local file of 64 KiB or more cannot be overwritten while the engine holds it.
Saving and Archives
- Engine:
block.saveToStringandblock.saveToArchivelay out pending changes first, so two saves with no edit between them write the same blocks. A text whose font still loads can differ between a save before and a save after the font arrives. While the settingfeatures/implicitUpdatesEnabledis off, they lay out nothing, as before. - Engine: A scene save lays out pending changes first, so what it writes no longer depends on whether the engine updated the scene before the save. While the setting
features/implicitUpdatesEnabledis off, the save lays out nothing, as before. - Engine: On Web and Android, an archive load now warns when a font setting or a color profile names a file that the archive lacks.
- Engine:
saveToArchivenow logs a warning for each resource that it cannot put into the archive, such as a font on an unreachable host. Before, the save gave no sign that the archive still depended on that URL. This was so on Android, on@cesdk/node, and in the browser withfileSystemUsageEnabledset tofalse.
Export
- Engine: An export now draws the largest source of an image fill’s source set, as it already does for a video fill. Before, it drew the smallest source that covered the export size, which could be an entry the engine generated. The export waits for the largest source and fails when that source cannot load.
- Engine (Android / Swift): With the P3 working color space on, an image export that contains only sRGB or untagged images is now an 8-bit sRGB file with the original colors. An export with a wide-gamut image, such as a Display P3 photo, stays a 16-bit Display P3 file.
- Engine:
exportAudioof a block, a track or a group now appliestimeOffset: the export startstimeOffsetseconds later in the block, as documented. Before, only anaudio/mp4export of an MP4 file applied it, and the other block, track and group exports ignored it. The defaultdurationis still the duration of the block, so pass a shorterdurationto end the export with the block. Anaudio/wavexport of a buffer withskipEncodingstill ignorestimeOffset. To keep the previous output, leave outtimeOffsetor pass 0. - Engine: A PDF export of a large page no longer fails when each image, gradient and blur on the page fits the device’s size limit at the scene DPI. A page with vector content only exports at any size. A drop shadow still needs the whole page to fit the limit. A PDF export now fails with an error instead of leaving out a shadow or blur that is too large. To export such a page without the shadow or blur, as before, set
dropShadow/enabledorblur/enabledof the block to false before the export, or lowerscene/dpi. - Engine: PDF export with
exportPdfWithHighCompatibilitynow writes an opaque image fill as JPEG whenpdfImageQualityis below 1, also when the image has effects. Before, the image stayed lossless with a soft mask when its edge fell between two pixels or its block had an opacity below 1. A blurred image stays lossless. - Engine: A video export of a page without audio blocks or videos now has no audio track. Before, the file had a silent audio track.
- Engine/Web: A video export with an audio bitrate that the browser cannot encode fails with an error that names the requested bitrate. Chrome and Edge on Windows encode AAC only at 96000, 128000, 160000 and 192000 bits per second. Before, the error said that the audio encoder stopped during the export.
- Engine/Web: A video export at a size the browser cannot encode now reports that the encoder could not start, and the editor asks for a different size. In Chrome, Edge and Firefox, the export fails before it draws a frame at that size.
Network Requests
- Engine: On Android, iOS, macOS, Linux, Windows and Node.js, the engine sends the name from your license in the Referer header only to
cdn.img.ly,staticimgly.comandapi.img.ly. Requests to other hosts no longer carry this header. Before, Android sent a value such assame-originas the Referer to every host. No setting sends it to other hosts. To identify your app to your own server, add a value to its URIs withsetURIResolver(setUriResolveron Android). - Engine: The engine percent-encodes
%and the characters outside printable ASCII in this header value. A license name with a character above U+00FF no longer makes these requests fail on Node.js.
Input Validation
- Engine:
setDurationnow returns an error for NaN instead of storing it. - Engine: Fade-in and fade-out durations now reject positive infinity in
setAudioFadeIn,setAudioFadeOutandsetDouble. The block duration still accepts infinity. - Engine/Node Native: An index that is not an integer now throws an argument error, as on Web. This covers
insertEffect,removeEffectand the insert and remove calls of masks, subshapes, text animators and modifiers. Before, an index such asNaNor0.5acted on the first entry.
Thumbnails
- Engine/Web:
block.generateThumbnailAtTimeOffsetreturns the PNG in a web worker. It encodes there with anOffscreenCanvas. - Engine: The callbacks of
generateAudioThumbnailSequenceandgenerateVideoThumbnailSequencenow run after the engine update that produced their results. An engine call inside an audio callback no longer delivers a chunk twice. A scene saved inside a video callback no longer stores the playback time of the thumbnail. Cancelling an audio or design block sequence now also stops the results that are still waiting for delivery.
Diagnostics
- Engine: When a save to a string refuses a resource because of its URI scheme, the error now names the
allowedResourceSchemesoption. For example, adddatato it to keepdata:URIs, which the default leaves out. - Engine:
findAllSettingslists the publicfeatures/*settings, such asfeatures/fontLineGapEnabledandfeatures/previewMotionBlurEnabled. It listed none before. - Engine (Web / Node): The evaluation banner is now a console warning, not a console error. The engine prints it when it runs without a license. Error monitoring that collects console errors no longer reports it. Node.js still prints it to stderr.
- Engine/Node Native: A load error for an installed platform package now names the cause first, such as a missing system library. It links the matching README section and recommends a reinstall only for a damaged file. When the platform package is missing, the advice now reinstalls
@cesdk/node-nativeinstead of adding the platform package to the project. The README now lists the system packages that every Linux workflow needs. - Renderer: A
--pageindex outside the scene, or a scene without pages, now prints an error, also without--verbose. Page indices start at 0.--helpnow lists the exit codes.
API Reference
- Engine: The API reference now states that
getInAnimation,getOutAnimation,getLoopAnimation,getBlur,getFillandgetShapereturn an invalid block when nothing is set. Check the result withisValid, not with0. - Engine: The API reference now states that
getTextListStyle,getTextListLevelandgetTextLineHeightreject a negative paragraph index, and howgroupandungroupkeep the blocks at their times on the timeline.
Platform Integration
- Engine/Node Native: On Windows, video and audio now use Media Foundation, which Windows 10, Windows 11 and Windows Server 2025 with Desktop Experience include. The package no longer needs the GStreamer runtime, also not to start. Where the runtime is installed, Media Foundation now replaces it for H.264, AAC and MP3. H.265 videos import through Media Foundation when the HEVC Video Extensions are installed. Otherwise they import through the GStreamer runtime, which needs a GPU that decodes H.265. An H.265 video with transparency keeps it only through the GStreamer runtime. Where only the HEVC Video Extensions can decode it, it imports without its transparency. Server Core and Nano Server containers have no Media Foundation. There the package uses the GStreamer runtime when it is installed, and otherwise reports every codec capability as unsupported.
- Editor + Camera (React Native):
@imgly/editor-react-nativeand@imgly/camera-react-nativesupport React Native’s experimental Swift Package Manager mode as an opt-in. That support now works on React Native 0.88 and on Expo SDK 58 (experiments.swiftPackageManager). Before, Xcode could not resolve the packages there. CocoaPods remains the default integration path. The Swift Package Manager section of each package README lists the steps each version needs: editor, camera.
Editor Fields
- Editor/Web: Enter in an inspector field now returns the focus to the canvas or the timeline only when you came to the field from there. Otherwise the focus stays in the field, so keyboard and screen reader users keep their place. In custom builder panels,
builder.TextInputandbuilder.NumberInputkeep the focus as before. The fields ofbuilder.ColorInputfollow the new rule. - Editor/Web: Enter in the resolution and pixel scale fields of the page size settings now applies the page format, as Enter in the width and height fields does.
- Editor/Web: Number fields now take only digits, one leading minus sign and one decimal point or comma, in the same way in every browser.
- Editor/Web: Number fields no longer take a number written with an exponent, such as
1e3. - Editor/Web: Number fields now take a pasted number with the minus sign
−or with full-width digits. - Editor/Web: Number fields now show the decimal separator of the browser language in every browser. Before, Firefox used the language of the page.
- Editor/Web: Escape in a number or text field now discards the typed value and shows the stored one again. Before, it stored the typed value. This also applies to
builder.NumberInputandbuilder.TextInput. To store a typed value, press Enter or move the focus out of the field. - Editor/Web: In a focused number or text field, such as the hex color field, Cmd+Z or Ctrl+Z now undoes the typing in that field, and Cmd+Shift+Z or Ctrl+Y redoes it. The field stores the result like typed text. Before, Cmd+Z or Ctrl+Z discarded the whole value that you had not confirmed yet.
- Editor/Web: The font size field shows Auto and Mixed inside the field. You can type a size over them, and the field keeps the focus when you pick a preset.
- Editor/Web: A line’s width now takes the same values as the stroke width: 0 and up, with no maximum of 30.
- Editor/Web: The trim duration in the inspector bar now has the inspector’s limits. At 2× speed, a 10 s clip now trims up to 5 s, in steps of 0.1 s, with two decimals.
- Editor/Web: The safety margin now steps by 0.125 in Inch scenes, as the bleed does.
- Editor/Web: A shape’s corner radius and the page gap now step by the design unit: 1 px, 0.1 mm or 0.01 in. The corner radius shows two decimals.
- Editor/Web: The cutout smoothing field now shows % after its value.
- Editor/Web: Each slider in an asset’s property panel now has its own name and
data-cy, such asasset-property-playback-duration, instead of the sharedasset-property-slider. Update test selectors that useasset-property-slider. - Editor/Web: The straighten slider in the crop bar now matches the inspector’s: it is disabled while the crop is, and its
data-cyiscrop-straighten. Before, it wascrop/straighten, so update test selectors that use it.
Editor
- Editor/Web: The zoom percentage stays the same when the editor moves to a screen with another pixel density, and 100% shows a design at the same size on every screen. A zoom chosen after the editor moves to another screen no longer switches back to auto-fit. The
zoom.toLevel,zoom.inandzoom.outactions take zoom levels per CSS pixel again, so 1 is 100%.engine.scene.getZoomLevel()stays in device pixels. - Editor/Android: A single tap on the selected page in page overview now opens it for editing.
- Editor/iOS: On the closed iPhone Duo, the navigation bar moves into the vertical strip beside the status bar and shows icons only, as the system apps do. The open iPhone Duo keeps the navigation bar at the top. Apps built with Xcode 27.1 or later get this; earlier Xcode versions keep the horizontal bar.
- Editor/Web: In the Photobook Editor starter kit,
getLayoutFormatnow returns the page format whose aspect ratio is closest to the page size, and can returna4. Before, every page taller than it is wide gotportrait.
Bug Fixes
Engine
- Two video exports that run at the same time on Android each keep their own audio.
- On iOS and macOS, an export no longer starts MP3 or AAC audio late when the same engine played or exported that audio before.
- An archive saved in the browser or with
@cesdk/node-nativenow stores its font settings and its color profile as the other platforms do. Engines older than 1.83.0 did not always find them in such an archive. - A font setting that names an axis of a variable font, such as
#wght=700, now loads with that axis from an archive. Engines older than 1.85.0 do not load the font of such a setting from an archive that 1.85.0 or later saves. - Saving an archive in the browser or with
@cesdk/node-nativeno longer crashes when the color profile of the document cannot be loaded. The archive then keeps the URI of the profile. - An asset source that registers from a JSON URL (
addLocalAssetSourceFromJSONURI) now finishes when a scene loads or is created during the download. Before, the scene load cancelled the download, and the registration failed with a fetch error, so the asset source was missing. - An audio export now starts each clip on the audio sample of its time offset, and stops it on the sample of its end. Before, a clip that started between two 10 ms steps of the export stayed silent until the next step. It also played up to 10 ms past its end, and a clip shorter than 10 ms could be missing.
getAudioTrackCountFromVideo,getAudioInfoFromVideo,createAudioFromVideoandcreateAudiosFromVideonow read the video that a video fill with a source set plays. Before, they read thefileURIof the fill, so they returned an error or the tracks of a different video.- Playback and export of audio at a speed between 1 and 1.0001 no longer corrupt memory. A video export with two or more such clips no longer crashes on macOS.
- Automatic font sizing now keeps every line of top-aligned text inside the frame height when
text/clipLinesOutsideOfFrameis false. Before, the text could grow far below the frame. The text still does not shrink belowtext/minAutomaticFontSize. Captions turn clipping off by default, so captions with automatic font sizing fit their frame too. - An SVG image fill with a blur or an effect now shows new image data after its buffer changes.
- Two SVG image fills with source sets and a blur or an effect no longer show the same image.
- In crop mode, an image that is smaller than its frame now grows smoothly when you drag a scale handle or pinch. It also moves with the pointer and with the crop position offsets. Before, the first drag or pinch made the image jump to the frame edges.
- A pinch in crop mode now follows the fingers. When the fingers return to their start spread, the image returns to its start size.
- Destroying a page or group also destroys the fills, shapes, effects, blurs, animations and transitions of the blocks inside it. Before, they stayed in the scene, and saving could fail after their image data was freed.
forceLoadAVResourceandunstable_isAVResourceLoadednow wait until a video or Lottie in abuffer://URI can be read, including the images that the Lottie references. Before, they reported such a buffer as loaded at once. A buffer that holds no video now reports a load error.- Camera position clamping respects uneven safe area insets. Before, clamped content sat half the inset difference toward the smaller inset.
- Changing the design unit now converts the custom dash lengths and the dash offset of a stroke, like its width.
- Destroying an engine now frees its internal event listeners. Before, a native app or a
@cesdk/node-nativeprocess kept about 10 KB for each engine that it created and destroyed. - A variable font that fails to load is no longer downloaded again on every text edit. A file that failed to load is no longer downloaded again when memory runs low.
- Fixed the
HardMixblend mode for colors with a channel at 0 or 1, for example pure red over black. It now sets each channel where the two colors add up to 1 or more, as in Photoshop. - A star shape draws at most 100,000 points. A larger
shape/star/pointsvalue draws 100,000 points instead of running out of memory. - A page in a scene with the
FreeorDepthStacklayout now exports with its bleed. A PDF export of it also writes its TrimBox and BleedBox. The default layout ofengine.scene.create()isFree. Before, such a page exported without its bleed. - A page that is not square and has a
page/marginScaleabove 1 now exports as the page and its bleed. Before, the export added an empty strip beyond the bleed. A rectangular page got it on its two longer sides, and a page of any other shape on every side. - An export now fails with an error when the engine has no memory to copy the scene’s buffers for it. Before, the export could contain empty data in place of those buffers, or the web engine stopped.
- A video export no longer changes the camera, the playhead or the visibility of the exported page. An audio export no longer moves the playhead.
- When an export or a cutout ends, also when it fails, the engine now frees the full-size image copies that it decoded. Before, these copies stayed in memory after the export. A later export decodes the images again.
- An image or a file that the engine unloaded right after it finished loading now loads again when a block needs it. Before, it could stay loading forever, for example after an archive save.
findNearestToViewPortCenterByTypeandfindNearestToViewPortCenterByKindno longer return blocks inside a hidden block, such as the children of a hidden group.- A flipped text with automatic width or height now grows like an unflipped text. Before, a centered, right-aligned or bottom-aligned flipped text moved away while you typed.
- Exporting a group, or a block that holds groups, now applies the opacity and blend mode of those groups, as a page export already did.
generateVideoThumbnailSequencenow renders a block inside a group or a track at the time it plays on the page. Before, it rendered the block too early, so the first frames were empty when the group or track started after 0 s.groupnow keeps each block at the time it plays on the page. Before, a block from inside another group kept a time relative to its old group. A block with a negative time offset moved the other blocks later. When the first block is inside a group, the new group goes into that group. A block that starts before that group then starts with it, and a block that ends after that group stops at its end.- H.265 (HEVC) video exports are no longer malformed in rare cases, which some video players could not play. A rare crash during an H.265 export is fixed too.
- A keyboard resize, and the
resizeaction, now changes a block with a Percent size by the step in design units. Before, the step was added to the fraction, so a step of 1 turned a width of 50% into 150%. - On Linux and in
@cesdk/node-nativeon Windows, a failed audio encoder no longer crashes the process. A video or MP4 audio export fails with an error instead. - Fixed a hang on Linux and in
@cesdk/node-nativeon Windows. Audio playback or an export stopped after two quick backward seeks in the same audio or video file. - An audio export as WAV that is longer than 11,184.81 seconds (3 h 6 min) now fails at once with an error. Before, the export returned a WAV file that held only the first part of the audio, and on the web it stopped the engine. Export longer audio in parts, with
timeOffsetandduration. - A saved archive names MP3 files
.mp3. Before, it named them.*mp3or.m2a. - When a video URL ends in
.mpgor.mpeg, a saved archive names the video file.mpg. Before, it named the file.m1v. - An outer stroke now stays outside the shape where an effect such as green screen makes the image transparent. Before, its inner half showed through.
- The page carousel keeps the current page in view and selected when the camera zoom clamping changes, for example when a sheet opens.
- Page thumbnails no longer crash the engine while page carousel mode is on.
- Page carousel mode now stays on when
resizeContentAwareresizes every page in one call. - A block with a Percent size or position now keeps these units when you drag a crop handle, or resize an image with an edge handle. Before, the size and the position became Absolute.
- A block with a Percent size or position that you drag to another page now keeps its size, its place and its units. Before, the position became Absolute and the block jumped, and a Percent size changed with the size of the new page.
- A block with a Percent position now moves by the right distance, for example when you drag or nudge it, and the position stays Percent. Before, the block jumped to another place.
- The crop mode of a block with a Percent size now shows every handle, and moves and scales the image with the pointer.
fillParentnow keeps a Percent size and sets it to the whole parent. Before, the size became Absolute, or many times larger than the parent when the parent had a Percent size.- A scene whose photos together pass the engine’s memory budget no longer freezes while the photos are on screen. Before, the engine loaded and decoded those photos again on every frame. On the iOS simulator, the budget is about 1 GB of photos.
- Exporting the audio of a block with
skipEncodingnow handles a WAV, MP3 or M4A file in itsbuffer://resource like the same file at a URL. Before, the export returned the file bytes as samples, so the audio played as noise or not at all. - Exporting the raw samples of a
buffer://resource asaudio/mp4withskipEncodingnow encodes them. Before, the export failed. - A recording in a
buffer://resource whose first bytes look like an MP3 frame header or a BMP file now plays and exports. Before, playback was silent, and so was every export except one withskipEncoding. - Fixed duplicated text when a variable placeholder contains several character styles. Placeholder selection and variable substitution remain unchanged.
- Fixed alignment, selection and list marker placement in mixed right-to-left paragraphs and their wrapped lines. The effective alignment API now reports the paragraph direction used for layout.
- Fixed missing fallback glyphs after asynchronously loading a text font.
- Fixed missing characters in right-to-left text when the selected font does not cover an entire character cluster.
- A color set on the fill block of a text now stays. Before, the text turned back to its previous color at the next update or text change.
- Text now draws each mark where its font places it, such as an Arabic letter dot or a Devanagari anusvara. Fonts that position these marks separately, such as Aref Ruqaa, rendered Arabic letters without their dots, and Palanquin Dark put the anusvara after its letter. A mark also stays on its letter with letter spacing and on a curved text path, because letter spacing now goes between characters and not inside one. Character ink boxes, line bounds, cutouts and text outlines include the marks.
- The selection highlight of text on a path no longer spreads out where the path bends sharply. A character at the end of an open path is now highlighted once, where it is drawn. In right-to-left text the highlight now covers the selected character, not the one next to it.
- The text range APIs, such as
getTextColorsandsetTextColor, return an out-of-range error whenfromortois one past the end of the text. Before, the engine could crash. - Fixed text run snapshots to include loaded fallback font ranges while preserving original text ranges and authored formatting.
- Groups with opacity or a blend mode now render correctly when a child is hidden, outside its time window, or left out of the export. Before, the page could disappear from the editor canvas, and exports and video frames turned black until the engine was disposed. A block that follows such a group no longer takes the group’s opacity.
- An image, PDF or video export now fails when the engine cannot decode one of its audio or video files. Before, the export never finished.
- An image, PDF or video export now fails when a video download fails while the video loads. Before, the export waited forever.
- A WAV or MP3 file in a video fill now puts the block in an error state, and an export of it fails. Before, it crashed the engine.
- Creating a scene from a file without a video track now fails. Before, it crashed for a WAV or MP3 file and made a page of size 0 for an MP4 file.
- WAV files with the extensible header, such as 24-bit files from ffmpeg, now play and export. This covers PCM and float samples.
- An MP4 file that ends inside its header now fails to load. Before, it could crash the engine.
- Loading a fragmented MP4 file with both audio and video no longer leaks memory.
- Loading a scene, an archive or a block string with a block type that the engine does not know returns an error. The current scene stays loaded. Before, the engine crashed.
- Text keeps drawing when the engine releases its font under memory pressure. Before, the app could crash.
- Exporting an H.265 (HEVC) video no longer leaks memory.
- On Linux without the licensed codec pack, and on Windows, a video export with a frame larger than the encoder’s limit of 36,864 macroblocks, which 4096x2304 fills exactly, now fails at once with an invalid-resolution error. Before, the export failed after it started and leaked memory. A video bitrate outside the encoder’s range of one bit per frame to 288 Mbit/s is now moved into that range instead of failing the export.
- A video export now fails with an error when its temporary file cannot grow, for example on a full disk. On the web, it also fails when it cannot create the file. On the other platforms, the export then keeps the video in memory. Before, the export returned an empty MP4 file or crashed.
- Fixed video fill thumbnails that could come out at the wrong size or crash the app.
- Fixed a crash on iOS when opening scenes with many pages in evaluation mode.
- Exporting the audio of an audio block that plays a WAV file as WAV now returns its audio. Before, it failed, or returned noise when the WAV file was in a buffer.
- An audio block in a scene loaded from an archive shows its waveform. A
buffer://resource that holds an MP3, M4A or WAV file is now decoded for the waveform, as it is for playback. - A drop shadow now keeps its direction while an animation or keyframes rotate its block, as it does for a rotation that
setRotationsets. Before, the shadow turned with the block. - The Pulsating and Breathing loop animations now keep a flipped block flipped. Before, they showed the block unflipped while they played. On a block with a keyframed
transform/scale/xortransform/scale/y, they scale the keyframed value. - A rect shape whose only rounded corner is the bottom-left one now rounds that corner of its image fill.
- The errors
BLOCK.VARIABLE_NAME_INVALID,ENCODE.EXPORT_OPTIONS_NOT_OBJECT,ENCODE.VIDEO_EXPORT_OPTIONS_NOT_OBJECTandENCODE.AUDIO_EXPORT_OPTIONS_NOT_OBJECTno longer log “Unknown placeholder” when they occur. Their hints no longer contain curly braces. - An MP4 or M4A audio track that starts later than its file now plays silence until it starts, then plays to its end.
- HE-AAC audio from Apple encoders now keeps its sound above 12 kHz on Windows, Linux, Android and the web. A trimmed HE-AAC clip on Windows and Linux now plays from its trim at full level.
- A video export and an MP4 audio export now play their audio in sync with the picture. Before, players started the audio 21 to 44 ms late. The file did not mark the silence that the AAC encoder puts before the first sound. On the web, this fix needs macOS or iOS, and on Android Google’s built-in AAC encoder. Audio that
createAudioFromVideoseparates from such a video keeps the same timing. - On Linux, an AAC audio file or the AAC audio of a video now plays from its first sound. Windows with the GStreamer runtime in place of Media Foundation gets the same fix. Before, playback and exports left out its first AAC frame, about 21 ms at 48 kHz.
- An MP3 file now plays from its first sound. Before, playback and exports played the silence that the MP3 encoder puts before it, 25 to 51 ms at 44.1 kHz depending on the platform. An MP3 file with a LAME or Xing header now reports a duration that is one MP3 frame shorter, 26 ms at 44.1 kHz.
- A text block on a page that is scrolled out of view now turns
Readywhen its font loads. Its line count, frame and bounding box then use that font. Before, it stayedPendinguntil its page came into view. A PDF import with a custom font resolver then waited 10 seconds and tightened the letter spacing on those pages. - A block on a page that is scrolled out of view now shows a change to its text, size or parent after the next update. Before, its line count, frame and bounding box kept the old values until the page came into view.
- Undo and redo of a page resize no longer crash the editor while page carousel mode or
unstable_enableCameraPositionClampingis on. The fix covers any change to a scene property, such asscene/pageDimensions. - Grouping and ungrouping now keep each block at the length it plays when its group ends before it. A block with keyframes keeps its full duration.
Engine/Web
- A video that is larger than the engine’s memory budget, such as a long video in an archive, now plays and exports. Before, the engine counted the whole file as memory and loaded the video again for each frame.
- An archive with a large video now loads every time. Before, the load could fail when the engine read the video before it had finished writing it to storage.
- A video or audio export fails at once with an audio encoder error when the browser’s audio encoder stops. Before, it failed only after the 10-second inactivity timeout, or with an error about a closed codec.
- On Windows, a video export with less than about 0.05 seconds of audio, such as a 2-frame video, now succeeds without sound. This applies to Chrome and Edge before version 157. Before, such an export failed after the 10-second inactivity timeout. An audio-only export that short now fails at once with an error.
- In Chrome on macOS, a character picked from the menu that opens when you hold a key now replaces the held character in edited text. Before, the held character stayed in the text.
- A video or an archive from a local upload that is missing in this browser now fails to load with one error. Before, it stayed loading forever, logged an error on every frame, and kept exports and archive saves of the scene waiting.
- An exception that a thumbnail or waveform callback throws is now logged to the console and no longer stops the engine update.
- When the browser cannot read a part of an uploaded video, playback and video export now skip the audio and the video of that part. Before, the audio stopped, the video froze, and a video export never finished.
- An audio export of a block that plays such a video now fails with an error. Before, it never finished.
- When a server sends no data for a part of a streamed video, playback and video export now skip that part. Before, they waited until a later response requested that part again.
- Moving the playhead back in a video now shows the frame at the new position without first showing earlier frames of the clip.
- Dragging the playhead forward in a long video now shows the frame at the new position right after the drag. Before, the video first played forward to that position. A video with few key frames can still decode forward to the new position.
- A remote MP4 video too large for the engine’s memory now fails to load with an error. Before, the engine stopped. The limit is about 2 GB where the engine can use 4 GB, and about 1 GB where it can use 2 GB.
- A remote MP4 video of about 700 MB to 1 GB now stays loaded. Before, the engine downloaded it again about every 3 seconds until it ran out of memory.
saveToArchivenow fails when the browser cannot write the archive to temporary storage. It used to return an incomplete archive or wait forever.saveToArchiveand a PDF export now fail when temporary storage stops responding to their writes for 30 seconds while the page is active. They used to wait forever.loadFromArchiveURLnow fails when the browser cannot create the temporary file for a video in the archive. It used to wait forever.- Audio no longer stutters or repeats short parts in playback and video exports. The fix applies to MP4 videos of 10 MiB or more that load from a URL ending in
.mp4. Video streaming is on by default. - Exporting a video in Chrome or Edge now finishes for H.264 videos that the hardware decoder holds back by up to 16 frames. 1080p videos at level 5.1 can do this. Before, the export stopped at its first frames and never finished.
- A video export now fails with an error that names the lost GPU context when the browser loses its WebGL context. This happens, for example, when the GPU process of the browser stops. Before, the export failed with an audio encoder error, or it delivered a video with empty frames.
- With
featureFlags.exportWorkeron, an image or PDF export now fails when the browser loses its WebGL context. The error names the lost GPU context. Before, the export returned an empty image.
Engine (Web / Node / Node Native)
engine.block.setSizewith a newsizeModeandmaintainCropin one call now adjusts the crop from the frame that the block has before the call. Before, the crop followed the old size read in the new mode, so the image was drawn at a wrong scale.
Engine/Node
- In
@cesdk/node-native, theasset.dropaction now rejects when the asset source has nofetchAsset.engine.asset.fetchAsset()now also rejects for afetchAssetoffalse,0or an empty string. Before, both ended the Node.js process. The rejection carries the same error code as in@cesdk/node. engine.block.exportAudio()in@cesdk/nodenow accepts the documentedskipEncodingoption, and anabortSignalthat isundefined, instead of throwing. It still refuses a signal, now with the reason: the engine cannot stop a running audio export.block.generateVideoThumbnailSequencedelivers its frames in@cesdk/nodeinstead of ending the Node.js process.block.generateThumbnailAtTimeOffsetrejects there, because it needs a canvas.
Engine/Node Native
- On macOS, a GPU export and the live view now free their graphics memory as they go. Before, each exported page and each frame that
readFramereturned added to that memory until the process ended, for example about 450 MB for a PDF of 300 pages. - On macOS, a video export with
renderDevice: 'cpu'now fails with an error. Before, it ended the process. This also applies to'auto'when it falls back to the CPU. - Pending video and audio exports now reject when their engine is disposed instead of leaving the caller waiting for a result.
engine.block.exportandexportWithColorMasknow finish inside Electron. Before, they never settled there, because Electron does not allow memory it does not own in a JavaScript buffer. In Electron the engine copies the result once.engine.block.setSizewithmaintainCrop: truenow adjusts the crop, so a resize to a new aspect ratio no longer stretches an image inCropfill mode. Before, the option had no effect.- On Windows, the engine no longer crashes when it stops a download that is still in progress, for example when a video or audio block is removed while it loads.
- On Windows, URLs with an explicit port, such as
http://localhost:8080/image.png, now load. Before, every request to such a URL failed. - On Windows, local files given as a
file://URL with a drive letter, such asfile:///C:/My%20Designs/image.png, now load. They also load when the app runs from a short folder such asC:\app. Before, local images, videos, audio and fonts failed to load. - On Linux, an imported MP4 video now shows its colors with the color tags of its file.
- On Windows,
@cesdk/node-nativenow loads in Electron. Before, Electron failed to load it with “The specified module could not be found”. - On Windows, a license API key now unlocks the engine. Before, every activation failed with “API service is unavailable”, because the engine sent a request body that it had already freed.
- On Linux, disposing one engine no longer aborts the process while another engine in the same process downloads resources. Disposing an engine now stops its own downloads once they are connected, instead of waiting up to 30 seconds for them to finish.
- On Linux, a long-running process keeps downloading resources in parallel after idle periods of more than 60 seconds.
Engine/Android
- HDR videos (HDR10 and HLG) now show correct colors on every device. Before, they looked washed out on Pixel phones, on many devices with Android 11 or older, and on others whose decoder cannot convert HDR.
- A video export fails with an audio encoder error when the device’s audio encoder fails. Before, the export never ended.
- The canvas now shows the page and its content on devices that run OpenGL ES through ANGLE on Qualcomm GPUs, such as Googlebooks. Before, parts of the page could stay empty in the editor, while exports were correct.
- A TextureView now shows the canvas without block noise on Googlebooks and other devices with Qualcomm Adreno X1 GPUs. Before, only a SurfaceView showed the canvas correctly there.
Engine/Swift
- Playback no longer crashes when an audio or video block is added, replaced or removed during playback, or when audio thumbnails are generated.
- A quick swipe between pages now changes the page while the app is busy, such as while page images load. Before, the canvas could return to the page where the swipe started.
- A video export on iOS now pauses as soon as the app leaves the foreground, and always continues when the app is active again. Before, a quick switch away and back could leave the export paused forever.
- A cancelled video export that is paused in the background now ends when the app is active again, and frees its memory. Before, it stayed in memory and used the main thread until the app quit.
Engine (Android / Swift)
- With the P3 working color space (
features/p3WorkingColorSpace) on, LUT filters no longer make images paler, and duotone, recolor, green screen and stripe fill colors are no longer oversaturated. Existing designs with these effects now look different, on screen and in exports.
Engine (Android / Swift / Node Native)
- An exported MP4 video now tags its colors as BT.709, so players show the colors of the design. Before, the video had no color tags, and players showed greens too saturated.
Editor/Web
- A
.ziparchive picked in Chrome on Windows now loads from OPFS like on other systems. - Tab no longer selects a block inside a hidden block.
- Number fields record one undo step per edit, such as a run of arrow keys or a held stepper button, and none when the value did not change.
- The gradient angle field records an undo step, and showing the speed panel’s duration field no longer records one.
- The shadow angle, a crop size with a locked aspect ratio and the font size keep the value you entered. Ending the edit no longer writes it again with older values.
- A slider changed with a screen reader records an undo step, and a slider gesture that changes nothing records none.
- A text background padding edit that lowers the corner radius limit clamps the radius in the same undo step. Opening the panel no longer changes the radius.
- Dragging, adding, removing or flipping a gradient’s color stops can now be undone. A drag records one undo step when you let go, and a run of arrow keys one when the stop loses focus.
- A number field step that the editor refuses or limits now continues from the stored value, and adds no empty undo step.
- A number field that writes each keystroke now records the stored value in its undo step when it loses focus, not the typed text.
- A number field now writes a typed value when it closes before Enter or a click elsewhere. A stepper held while its field closes now adds its undo step.
- Screen readers no longer announce a valid number field value, such as 2.5 in a field that steps by 1, as an invalid entry.
- Scrolling the mouse wheel over a focused number field no longer changes its value in Chrome and Edge, as in other browsers.
- A disabled
builder.NumberInputnow also disables its stepper buttons, so they no longer change the value. - A number field increment or decrement from a screen reader now applies at once, as the arrow keys do.
- Typing a letter or a space over the selected value of a number field now changes nothing. Before, it emptied the field.
- Number fields read a typed or pasted comma as the decimal separator in every browser language. Before,
1,5became 15 in an English browser. - The trim duration field in the inspector bar is as wide as its value needs from the start, instead of first appearing too wide.
- A disabled font size or bleed field no longer opens its preset list.
- The Transform panel now sets the typed size on a block with a Percent size. Before, with the aspect ratio locked, the block grew to many times the size of its parent.
- The Transform panel now sets the typed X and Y position on a block with a Percent position. Before, it stored the typed number as a fraction of the parent, so the block moved far away.
- A slider for a whole-number property stores the nearest whole number. Before, a slider whose step was a fraction could fail to store the value.
- A text field that you empty and confirm shows its kept value again instead of staying empty.
- The hex color field shows the current color again after you confirm an invalid hex code.
- The speed duration field shows one tooltip instead of two.
- Clearing the asset library search no longer runs the search typed just before it.
- Leaving the stroke dash pattern field with an invalid pattern restores the pattern from before the edit, instead of keeping part of the typed one.
- Leaving a caption text, caption time or stroke dash pattern field records an undo step only when the edit changed it.
- A field’s tooltip stays hidden when you click into the field and move the pointer away before it shows, so Escape reaches the field.
builder.TextInputwithisDisabled: truenow renders a disabled input.builder.TextAreanow places its label on the left forinputLabelPosition: 'left', and shows asuffixalso without aninputLabel.- Screen readers name the builder
NumberInput,Slider,TextInputandTextAreafields by theirinputLabel. - Screen readers name the caption start and end time fields, the asset library search, the search of a searchable
builder.Selectand the gradient angle field. The two searches take their name from the new translation keycommon.search. - Focused number, text, slider and prompt fields now show their focus ring in Windows high contrast mode.
- Screen readers now announce the new value after a number field’s stepper button is pressed while the focus is elsewhere.
- Screen readers now read a number field’s tooltip text. A disabled bleed or safety margin field stays reachable with the keyboard, and screen readers say why it is disabled.
- Screen readers now read the name of a preset or dropdown list, and a closed preset field no longer points at a missing list. The speed field’s tooltip now reaches screen readers.
- Screen readers now read the percent a slider shows, such as 50% for opacity, instead of its raw value.
- Grouping clips in a video scene keeps the new group at the time of its clips. Before, the group moved to the start of the timeline.
- Editing, deleting, grouping or ungrouping clips in a group now keeps each clip at the length it plays. Before, the clips grew to their full length when the group was shorter than them. A Text Combination added near the end of a video is such a group.
Editor/Android
- Fixed audio clips appearing partially dimmed in the timeline after shortening the background track.
Editor/iOS
- A zoom level you set now survives a rotation, a fold or a window resize. Before, the editor re-fit the page to the new window and the zoom was lost.
- The page stays centered beside a side safe area, such as the status strip of the closed iPhone Duo, also when nothing is selected in the Design editor.
- On the unfolded iPhone Duo, dragging or trimming a timeline clip follows your finger. Before, the clip moved at a right angle to it.
Editor + Camera (React Native)
- In React Native’s experimental Swift Package Manager mode, an upgrade of
@imgly/editor-react-nativeor@imgly/camera-react-nativenow resolves the new IMGLYUI version. Before, Xcode could keep the IMGLYUI version of the previous release, so the build failed or the app showed the old editor. CocoaPods is not affected.