Search Docs
Loading...
Skip to content

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. Pass framerate: 30 to keep the previous output.
  • Engine (Swift + Android): VideoExportOptions.framerate (Swift) and ExportVideoOptions.frameRate (Android) are now Float? and default to nil (Swift) or null (Android), which uses the scene’s frame rate. Code that reads the property needs options.framerate ?? 30 (Swift) or options.frameRate ?: 30F (Android) to get the previous value. Pass framerate: 30 (Swift) or frameRate = 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 coverTimeOffset uses 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/mp4 export 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/node encodes 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.
  • 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 exportPendingTimeoutSeconds setting to change the limit, or to 0 to wait without limit. A block that you set to pending with setState does 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, so array() now throws UnsupportedOperationException. Read them with get(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.animations library, a caption style or the editor’s animation actions applies it to every caption on the track. A caption whose appearance/animation scope 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, setOutAnimation or setLoopAnimation on it after adding it to the track.
  • Engine: An animation applied from the ly.img.animations library, a caption style, or the animation.in, animation.out and animation.loop actions 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.animations library to one block now returns an error when its appearance/animation scope 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): setTextCursorRange now 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, getTextRuns and getTextCursorRange, count a text variable such as {{name}} by its characters, as in text/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 of text/text, as the range parameters on Web, Android and iOS already describe.

Layout

  • Engine: setWidth, setHeight and setSize with maintainCrop now 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 by getWidth() and divide it by getFrameWidth(), or use the height getters.

Resource Loading

  • Engine/Node Native: CreativeEngine.init now rejects with an error when the engine cannot parse baseURL, 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 %5B for [. A non-ASCII letter in a URL needs no change: init now encodes it, as @cesdk/node does.
  • Engine (Web / Node): In the browser and in @cesdk/node, a setURIResolver function that throws or returns no string now logs a warning, and the engine loads the URI unchanged, as @cesdk/node-native already 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 an Error state.

API Types

  • Engine/Swift: BlockState.error carries the EngineError behind the failure as engineError when the engine knows it. The case is now error(BlockStateError, engineError: EngineError?). Update each pattern match: case .error(.videoDecoding) becomes case .error(.videoDecoding, _), and case let .error(kind) becomes case 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 as case let .error(kind, engineError).
  • Engine/Android: CMYKProfileInfo has two new properties, name and uri. 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.load and scene.loadFromArchiveURL accept an options object with an onProgress callback (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-native reports the progress of an archive, and @cesdk/node gives no call. On Android, scene.load and scene.loadArchive take an onProgress callback for the progress of an archive. On Swift, scene.load(from:) with a URL and scene.loadArchive(from:) take an onProgress closure for the progress of an archive. On Android and Swift, it gets calls only with the setting features/fileSystemUsageEnabled on.
  • 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 no preview.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.export overload that writes a PDF into an OutputStream while 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.export overload that returns a PDF as a cold Flow<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. null and 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/mp4 export 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 audioCodec export option (AudioCodec.Auto, AudioCodec.AAC or AudioCodec.Opus) selects the audio codec. Auto is 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.checkSupport action now answers true in 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.getMarkers and scene.removeMarker (experimental) to add named points or spans, in seconds, to the scene timeline. Swift: scene.addMarker(name:time:duration:), scene.getMarkers() and scene.removeMarker(at:).
  • Engine: Added composition groups (experimental). When group/composition is true, the children of the group run on the group’s own clock.
  • Engine: playback/speed is now available on groups (experimental) and scales the clock of a composition group’s children.
  • Engine: Added playback/timeRemap and playback/timeRemapEnabled on 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 getVec2 and setVec2 and the property type Vec2 (experimental). The block property position and the shape property shape/position are Vec2 properties, so one call reads or writes both coordinates. A write that fails changes neither coordinate.
  • Engine: Added setKeyframes, getKeyframes, getKeyframeMode, removeKeyframes, getAnimatedProperties and isPropertyAnimatable (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) and shape/position (a shape), with spatialOut and spatialIn tangents for curved motion paths. position/x and position/y stay as they are.
  • Engine: Added the graphic, text and caption block properties transform/scale/x and transform/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 the layer/flip scope.
  • Engine: Added the graphic, text and caption block properties transform/anchor/x and transform/anchor/y (experimental). They set the point that rotation, skew and scale pivot on. Added transform/skew and transform/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 a rotation keyframe 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 setTransformParent and getTransformParent (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): setKeyframes refuses 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: setKeyframes rejects 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/x and position/y tracks into one position track. 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: setKeyframes and removeKeyframes need the appearance/animation scope and the scope that the setter of the property needs, such as layer/move for position. A transform/scale/x or transform/scale/y keyframe that flips the block also needs the layer/flip scope.
  • Engine: A rotation keyframe track turns a graphic or text block without an anchor around its center, as setRotation does. Set transform/anchor/x or transform/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. fillParent ends 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 zero transform/scale/x or transform/scale/y scales, 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/shutterAngle takes 0 to 720 degrees, default 180. motionBlur/shutterPhase takes -360 to 360 degrees, default 0. motionBlur/samples takes 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/previewMotionBlurEnabled setting to false to 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/mode property (experimental). In the new Block mode, 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, the Block mode blurs the page and its content together, cut at the page edge. Linear, radial and mirrored blurs are rejected on a group. Fill stays the default and blurs the fill inside the block’s shape, as before.
  • Engine: Added the tint, fill, tritone, levels, invert, threshold and shift_channels effects (experimental). Scenes that use them do not open in earlier versions.
  • Engine: Added the corner_pin, bulge, wave_warp and displacement_map effects (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) and engine.block.getEffectSource(effect) (experimental) to set the block that a displacement map effect reads as its map. Swift: setEffectSource(effectID:source:) and getEffectSource(effectID:). Android: setEffectSource(effectBlock, source) and getEffectSource(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_pin effect 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_wipe and venetian_blinds effects (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 stroke effect (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 with setShape, and attach it with appendMask or insertMask. A block shows only the area its masks cover. Each mask has the properties mask/mode (Add, Subtract, Intersect, Lighten, Darken, Difference or None), mask/opacity, mask/feather, mask/expansion and mask/inverted. SVG export ignores the mask opacity and feather. Scenes with masks do not open in earlier versions.
  • Engine: Added setMatte and getMatte (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: setMatte needs the layer/clipping scope of the masked block and the layer/visibility scope of a new source block, because a matte source stops drawing on its own. A new mode for the same source needs no layer/visibility scope.
  • Engine: insertChild and appendChild fail 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 with shape/compound/mode (Merge, Add, Subtract, Intersect, Exclude), and the result stays editable. Manage the subshapes with getSubshapes, insertSubshape, appendSubshape and removeSubshape. Place each subshape with shape/position/x, shape/position/y, shape/width, shape/height, shape/rotation, shape/skew and shape/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. insertSubshape and appendSubshape return an error for a deeper subshape, and loading a scene drops the deeper subshapes.
  • Engine: The Vector edit 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, insertModifier and removeModifier manage 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 is trim, which keeps only a part of the path (modifier/trim/start, end, offset and mode). findByType finds 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_bloat and twist (experimental). All their properties can be animated with keyframes. Scenes that use them do not open in earlier versions.
  • Engine: round_corners rounds the corners of a shape. offset grows or shrinks it. zigzag adds peaks and valleys to its edges. pucker_bloat pulls its corners towards its center or pushes them away. twist twists it around a center.
  • Engine: Added the repeater shape modifier (experimental). It draws modifier/repeater/copies copies of a shape, from 0 to 1024. Each copy moves one step further by positionX and positionY. It also scales by scaleX and scaleY and turns by rotation, about anchorX and anchorY. offset shifts every copy by whole steps, and composite draws each copy below or above the one before. The opacity goes from startOpacity towards endOpacity. 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 repeater modifier 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, innerRoundness and rotation (experimental). shape/star/innerDiameter still works and now reports the ratio of the inner to the outer radius.
  • Engine: Added fill/gradient/radial/focalX and focalY (experimental) to move the start of a radial gradient away from its center.
  • Engine: Added the stroke/miterLimit property (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 setStrokeFill and getStrokeFill (experimental) to paint the stroke of a graphic block or page with a color, gradient or stripe fill.

Text

  • Engine: The setting features/fontLineGapEnabled is public. It defaults to true, so the line height of text includes the line gap that a font declares. Set it to false for the line heights of engine versions before 1.73.
  • Engine (Web / Swift): Added block.setTextBaselineShift and block.getTextBaselineShifts (experimental) to move a text range up or down from the baseline, in design units. Swift: block.setTextBaselineShift(_:baselineShift:in:) and block.getTextBaselineShifts(_:in:).
  • Engine: Added text animators (experimental). createTextAnimator, appendTextAnimator, insertTextAnimator, removeTextAnimator, getTextAnimators and supportsTextAnimators manage 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 Line writing 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/text property 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.alignment feature 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.findAll still lists only text variables.
  • Engine: A block property binds to a variable with setVariableBinding (experimental) and then follows every change of the variable. getVariableBinding, removeVariableBinding and findAllVariableBindings read 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, getType and findAllOfType (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:) and findAll(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 .lottie files (experimental). When a .lottie file cannot be read, forceLoadAVResource fails 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, https and bundle URIs, 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.saveToArchive and block.saveToArchive put 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 maxImageSize setting, 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 .lottie archive. 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 .lottie archive included.

Block State Errors

  • Engine (Web / Node / Node Native / Android): An Error state from block.getState carries the error behind it in engineError when the engine knows it (Web, Node, Node Native). On Android, BlockState.Error has engineError, the EngineException behind the error, when the engine knows it. On Web, it has the same fields as a thrown engine error. A .lottie file 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: getCMYKProfileInfo now also reports the profile’s name, from its ICC description, and the uri the document names it by. Pass that uri to setCMYKProfile to put the same profile on another document in the same engine. A save and load rewrites the uri, and a profile that has not finished loading reports no name.
  • Engine (Web / Node): unstable_setImportedImageDefinition() now accepts colorKeyMask and a single mask with Stencil or Soft kind 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.setSafeAreaInsets and editor.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/enabled engine 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/select scope 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) or InspectorBar.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) or separateAudioForSelection (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.expand feature 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/animation scope 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-js exports the new @cesdk/engine types Keyframe, KeyframeValue, KeyframeEasing, KeyframePathVertex, TrackMode, MatteMode, BlockMatte, SceneMarker, VariableType, ModifierType and BlockStateEngineError.

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 setURIResolver resolver (setUriResolver on 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/node stopped 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 createBuffer moves 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.saveToString and block.saveToArchive lay 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 setting features/implicitUpdatesEnabled is 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/implicitUpdatesEnabled is 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: saveToArchive now 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 with fileSystemUsageEnabled set to false.

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: exportAudio of a block, a track or a group now applies timeOffset: the export starts timeOffset seconds later in the block, as documented. Before, only an audio/mp4 export of an MP4 file applied it, and the other block, track and group exports ignored it. The default duration is still the duration of the block, so pass a shorter duration to end the export with the block. An audio/wav export of a buffer with skipEncoding still ignores timeOffset. To keep the previous output, leave out timeOffset or 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/enabled or blur/enabled of the block to false before the export, or lower scene/dpi.
  • Engine: PDF export with exportPdfWithHighCompatibility now writes an opaque image fill as JPEG when pdfImageQuality is 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.com and api.img.ly. Requests to other hosts no longer carry this header. Before, Android sent a value such as same-origin as 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 with setURIResolver (setUriResolver on 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: setDuration now returns an error for NaN instead of storing it.
  • Engine: Fade-in and fade-out durations now reject positive infinity in setAudioFadeIn, setAudioFadeOut and setDouble. 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, removeEffect and the insert and remove calls of masks, subshapes, text animators and modifiers. Before, an index such as NaN or 0.5 acted on the first entry.

Thumbnails

  • Engine/Web: block.generateThumbnailAtTimeOffset returns the PNG in a web worker. It encodes there with an OffscreenCanvas.
  • Engine: The callbacks of generateAudioThumbnailSequence and generateVideoThumbnailSequence now 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 allowedResourceSchemes option. For example, add data to it to keep data: URIs, which the default leaves out.
  • Engine: findAllSettings lists the public features/* settings, such as features/fontLineGapEnabled and features/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-native instead of adding the platform package to the project. The README now lists the system packages that every Linux workflow needs.
  • Renderer: A --page index outside the scene, or a scene without pages, now prints an error, also without --verbose. Page indices start at 0. --help now lists the exit codes.

API Reference

  • Engine: The API reference now states that getInAnimation, getOutAnimation, getLoopAnimation, getBlur, getFill and getShape return an invalid block when nothing is set. Check the result with isValid, not with 0.
  • Engine: The API reference now states that getTextListStyle, getTextListLevel and getTextLineHeight reject a negative paragraph index, and how group and ungroup keep 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-native and @imgly/camera-react-native support 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.TextInput and builder.NumberInput keep the focus as before. The fields of builder.ColorInput follow 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.NumberInput and builder.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 as asset-property-playback-duration, instead of the shared asset-property-slider. Update test selectors that use asset-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-cy is crop-straighten. Before, it was crop/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.in and zoom.out actions 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, getLayoutFormat now returns the page format whose aspect ratio is closest to the page size, and can return a4. Before, every page taller than it is wide got portrait.

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-native now 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-native no 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, createAudioFromVideo and createAudiosFromVideo now read the video that a video fill with a source set plays. Before, they read the fileURI of 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/clipLinesOutsideOfFrame is false. Before, the text could grow far below the frame. The text still does not shrink below text/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.
  • forceLoadAVResource and unstable_isAVResourceLoaded now wait until a video or Lottie in a buffer:// 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-native process 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 HardMix blend 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/points value draws 100,000 points instead of running out of memory.
  • A page in a scene with the Free or DepthStack layout now exports with its bleed. A PDF export of it also writes its TrimBox and BleedBox. The default layout of engine.scene.create() is Free. Before, such a page exported without its bleed.
  • A page that is not square and has a page/marginScale above 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.
  • findNearestToViewPortCenterByType and findNearestToViewPortCenterByKind no 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.
  • generateVideoThumbnailSequence now 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.
  • group now 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 resize action, 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-native on 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-native on 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 timeOffset and duration.
  • A saved archive names MP3 files .mp3. Before, it named them .*mp3 or .m2a.
  • When a video URL ends in .mpg or .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 resizeContentAware resizes 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.
  • fillParent now 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 skipEncoding now handles a WAV, MP3 or M4A file in its buffer:// 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 as audio/mp4 with skipEncoding now 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 with skipEncoding.
  • 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 getTextColors and setTextColor, return an out-of-range error when from or to is 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 setRotation sets. 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/x or transform/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_OBJECT and ENCODE.AUDIO_EXPORT_OPTIONS_NOT_OBJECT no 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 createAudioFromVideo separates 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 Ready when its font loads. Its line count, frame and bounding box then use that font. Before, it stayed Pending until 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_enableCameraPositionClamping is on. The fix covers any change to a scene property, such as scene/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.
  • saveToArchive now fails when the browser cannot write the archive to temporary storage. It used to return an incomplete archive or wait forever.
  • saveToArchive and 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.
  • loadFromArchiveURL now 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.exportWorker on, 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.setSize with a new sizeMode and maintainCrop in 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, the asset.drop action now rejects when the asset source has no fetchAsset. engine.asset.fetchAsset() now also rejects for a fetchAsset of false, 0 or 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/node now accepts the documented skipEncoding option, and an abortSignal that is undefined, instead of throwing. It still refuses a signal, now with the reason: the engine cannot stop a running audio export.
  • block.generateVideoThumbnailSequence delivers its frames in @cesdk/node instead of ending the Node.js process. block.generateThumbnailAtTimeOffset rejects 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 readFrame returned 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.export and exportWithColorMask now 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.setSize with maintainCrop: true now adjusts the crop, so a resize to a new aspect ratio no longer stretches an image in Crop fill 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 as file:///C:/My%20Designs/image.png, now load. They also load when the app runs from a short folder such as C:\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-native now 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 .zip archive 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.NumberInput now 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,5 became 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.TextInput with isDisabled: true now renders a disabled input.
  • builder.TextArea now places its label on the left for inputLabelPosition: 'left', and shows a suffix also without an inputLabel.
  • Screen readers name the builder NumberInput, Slider, TextInput and TextArea fields by their inputLabel.
  • Screen readers name the caption start and end time fields, the asset library search, the search of a searchable builder.Select and the gradient angle field. The two searches take their name from the new translation key common.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-native or @imgly/camera-react-native now 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.