Search Docs
Loading...
Skip to content

Combine Shapes

Combine multiple shapes using boolean operations to create custom compound designs programmatically.

Boolean operations preview showing Union, Difference, Intersection, and XOR results

7 mins
estimated time
GitHub

CE.SDK provides four boolean operations for graphic and text blocks: Union, Difference, Intersection, and XOR. Use them to merge simple primitives, create cutouts, isolate overlapping areas, or remove overlaps from a compound shape.

This guide covers checking combinability, applying the four operations with Android’s BooleanOperation enum, controlling fill inheritance through block order, and avoiding common scope issues.

Understanding Boolean Operations#

Boolean operations create a new block from the geometry of multiple input blocks. The result replaces the input blocks when the required scopes allow CE.SDK to duplicate and destroy them.

OperationAndroid enumResult
UnionBooleanOperation.UNIONMerges all block areas into one compound shape
DifferenceBooleanOperation.DIFFERENCESubtracts upper blocks from the bottom-most base block
IntersectionBooleanOperation.INTERSECTIONKeeps only the areas where all blocks overlap
XORBooleanOperation.XORKeeps non-overlapping areas and removes intersections

For Union and XOR, the new block inherits the fill from the top-most block. For Difference and Intersection, it inherits the fill from the bottom-most block. The operation order follows visual stacking order, not the order of the block IDs in the list. Reorder blocks with engine.block.bringToFront(...) or engine.block.sendToBack(...) before combining when fill inheritance matters.

Check If Blocks Can Be Combined#

Before combining blocks, call engine.block.isCombinable(...). It returns true only when the selection contains at least two compatible graphic or text blocks on the same page and each block has the lifecycle/duplicate scope enabled.

val unionBlocks = listOf(circle1, circle2, circle3)
val canCombineUnion = engine.block.isCombinable(unionBlocks)
check(canCombineUnion) { "Choose graphic or text blocks that can be combined." }

Use the same set of blocks for the later combine(...) call so the checked selection and the mutated selection cannot drift.

Combining with Union#

Union merges several blocks into one compound outline. In this sample, three overlapping circles become a single block and inherit the blue fill from the top-most circle.

val unionResult = engine.block.combine(
blocks = unionBlocks,
op = BooleanOperation.UNION,
)

Use Union for merged logos, compound icons, and shapes built from simple primitives.

Combining with Difference#

Difference subtracts upper blocks from the bottom-most base block. Place the base block behind the subtracting blocks before combining so the result keeps the intended fill.

engine.block.sendToBack(imageBlock)
// Load the image fill and text font resources before combining.
engine.block.forceLoadResources(listOf(imageBlock, textBlock))
val differenceBlocks = listOf(imageBlock, textBlock)
check(engine.block.isCombinable(differenceBlocks))
val differenceResult = engine.block.combine(
blocks = differenceBlocks,
op = BooleanOperation.DIFFERENCE,
)

The sample places an image block behind a text block, loads the image resource, and then removes the text shape from the image.

engine.block.forceLoadResources(...) loads both the image fill and text font resources before the operation reads their shapes.

Combining with Intersection#

Intersection keeps only the area shared by all selected blocks. The result inherits the bottom-most block’s fill. The sample uses two overlapping circles to create a lens-shaped result.

val intersectionBlocks = listOf(intersectionCircle1, intersectionCircle2)
check(engine.block.isCombinable(intersectionBlocks))
val intersectionResult = engine.block.combine(
blocks = intersectionBlocks,
op = BooleanOperation.INTERSECTION,
)

Use Intersection for overlap effects, lens shapes, and geometric masks.

Combining with XOR#

XOR keeps the non-overlapping areas of the selected blocks and removes their intersections.

val xorBlocks = listOf(xorCircle1, xorCircle2)
check(engine.block.isCombinable(xorBlocks))
val xorResult = engine.block.combine(
blocks = xorBlocks,
op = BooleanOperation.XOR,
)

Use XOR for exclusion shapes, cut-through overlaps, and inverted intersection patterns.

Understanding Fill Inheritance#

Combined blocks inherit fill and related appearance properties from the prioritized block. For Union and XOR, the prioritized block is the top-most input block. For Difference and Intersection, it is the bottom-most input block.

Control this before combining by moving the block whose fill should be inherited with engine.block.bringToFront(...) or engine.block.sendToBack(...).

Scope Requirements#

Combining blocks uses the same scope system as other block mutations:

  • lifecycle/duplicate must be enabled for every input block. engine.block.isCombinable(...) checks this, along with the two-block and same-page requirements, before you call combine(...).
  • lifecycle/destroy must be enabled when the input blocks should be replaced by the combined result.

If scoped content blocks a combination workflow, inspect the relevant scope with engine.block.isScopeEnabled(...) and update it with engine.block.setScopeEnabled(...) only when your editing rules allow that change.

Troubleshooting#

Combination Fails#

  • Check the block list with engine.block.isCombinable(...) before calling combine(...).
  • Make sure every input is a graphic or text block.
  • Select at least two compatible blocks on the same page.
  • Verify that the selected blocks still exist and have not already been consumed by a previous boolean operation.

Wrong Fill on the Result#

  • For Union and XOR, move the block whose fill should be inherited to the front.
  • For Difference and Intersection, send the block whose fill should be inherited to the back.
  • Re-run the combinability check after changing the selection or replacing any block.

Original Blocks Remain#

If the combined result appears but the original blocks remain, the input blocks may not have lifecycle/destroy enabled. Check that scope with engine.block.isScopeEnabled(...) and only enable it with engine.block.setScopeEnabled(...) when your app’s editing rules allow the originals to be removed.

Unexpected Shape Result#

  • Boolean operations use block order. Union and XOR start from the highest sort order; Difference and Intersection start from the lowest sort order.
  • Control visual stacking with engine.block.bringToFront(...) or engine.block.sendToBack(...) before combining.
  • Load image fill and text font resources before combining those blocks so the operation can resolve their shapes.

API Reference#

MethodDescription
engine.block.isCombinable(blocks=_)Check whether blocks can be combined
engine.block.combine(blocks=_, op=_)Perform a boolean operation on compatible blocks
engine.block.create(blockType=DesignBlockType.Graphic)Create a graphic block
engine.block.create(blockType=DesignBlockType.Text)Create a text block
engine.block.createShape(type=_)Create a shape for a graphic block
engine.block.setShape(block=_, shape=_)Assign a shape to a graphic block
engine.block.createFill(fillType=_)Create a fill block
engine.block.setFill(block=_, fill=_)Assign a fill to a block
engine.block.setFillSolidColor(block=_, color=_)Set a solid color fill
Color.fromRGBA(r=_, g=_, b=_, a=_)Create an RGBA color value
engine.block.setWidth(block=_, value=_)Set the block width
engine.block.setHeight(block=_, value=_)Set the block height
engine.block.setPositionX(block=_, value=_)Set the block’s x position
engine.block.setPositionY(block=_, value=_)Set the block’s y position
engine.block.appendChild(parent=_, child=_)Add a block to a parent
engine.block.replaceText(block=_, text=_)Replace text block content
engine.block.setTextFontSize(block=_, fontSize=_)Set text size
engine.block.setString(block=_, property="fill/image/imageFileURI", value=_)Set an image fill URI
engine.block.forceLoadResources(blocks=_)Load resources needed by blocks
engine.block.bringToFront(block=_)Move a block to the highest sort order
engine.block.sendToBack(block=_)Move a block to the lowest sort order
engine.block.isScopeEnabled(block=_, key=_)Check whether a scope is enabled
engine.block.setScopeEnabled(block=_, key=_, enabled=_)Enable or disable a scope