Validate a design before export by detecting elements outside the page, protruding content, obscured text, and unfilled placeholders.
Pre-export validation catches layout and content issues before export, preventing problems like cropped content, hidden text, and incomplete designs in the final output. The checks shown here run against CE.SDK Engine block APIs, so you can call them before your own export step.
Each check returns ValidationIssue values categorized by severity. Errors describe content that should block export; warnings describe content that may still export but needs review.
data class BoundingBox( val minX: Float, val minY: Float, val maxX: Float, val maxY: Float,)
enum class ValidationSeverity { ERROR, WARNING,}
enum class ValidationIssueKind { OUTSIDE_PAGE, PROTRUDING, TEXT_OBSCURED, UNFILLED_PLACEHOLDER,}
data class ValidationIssue( val kind: ValidationIssueKind, val severity: ValidationSeverity, val block: DesignBlock, val blockName: String, val message: String,)
data class ValidationResult( val errors: List<ValidationIssue>, val warnings: List<ValidationIssue>,)
private fun displayName( engine: Engine, block: DesignBlock,): String { val name = engine.block.getName(block) if (name.isNotBlank()) return name
val kind = engine.block.getKind(block).substringAfterLast("/") return kind.replaceFirstChar { it.uppercaseChar() }}Getting Element Bounds#
Every spatial check compares block positions in global coordinates. The getGlobalBoundingBox* methods return the axis-aligned box components in the scene’s global coordinate space, the overlap helper turns two boxes into a ratio from 0 (fully outside) to 1 (fully inside), and the descendant helper keeps later checks scoped to visible, exportable blocks on the page being exported.
private fun boundingBox( engine: Engine, block: DesignBlock,): BoundingBox { val x = engine.block.getGlobalBoundingBoxX(block) val y = engine.block.getGlobalBoundingBoxY(block) val width = engine.block.getGlobalBoundingBoxWidth(block) val height = engine.block.getGlobalBoundingBoxHeight(block) return BoundingBox(minX = x, minY = y, maxX = x + width, maxY = y + height)}
private fun overlapRatio( first: BoundingBox, second: BoundingBox,): Float { val overlapWidth = maxOf( 0F, minOf(first.maxX, second.maxX) - maxOf(first.minX, second.minX), ) val overlapHeight = maxOf( 0F, minOf(first.maxY, second.maxY) - maxOf(first.minY, second.minY), ) val firstArea = (first.maxX - first.minX) * (first.maxY - first.minY) return if (firstArea <= 0F) 0F else (overlapWidth * overlapHeight) / firstArea}
private fun pageDescendantsInRenderOrder( engine: Engine, parent: DesignBlock,): List<DesignBlock> = engine.block.getChildren(parent).flatMap { child -> listOf(child) + pageDescendantsInRenderOrder(engine = engine, parent = child)}
private fun validationCandidates( engine: Engine, page: DesignBlock,): List<DesignBlock> = pageDescendantsInRenderOrder(engine = engine, parent = page).filter { block -> if (!engine.block.isValid(block)) return@filter false if (!engine.block.isVisible(block)) return@filter false if (!engine.block.isIncludedInExport(block)) return@filter false
val blockType = engine.block.getType(block) blockType == DesignBlockType.Text.key || blockType == DesignBlockType.Graphic.key}Detecting Elements Outside the Page#
Elements completely outside the page are missing from the export. Scan visible text and graphic descendants that are included in export, compare each block with the page bounds, and flag blocks whose overlap ratio is zero.
private fun findOutsideBlocks( engine: Engine, page: DesignBlock,): List<ValidationIssue> { val pageBounds = boundingBox(engine = engine, block = page) val candidates = validationCandidates(engine = engine, page = page)
return candidates.mapNotNull { block -> val blockBounds = boundingBox(engine = engine, block = block) if (overlapRatio(blockBounds, pageBounds) == 0F) { ValidationIssue( kind = ValidationIssueKind.OUTSIDE_PAGE, severity = ValidationSeverity.ERROR, block = block, blockName = displayName(engine = engine, block = block), message = "Element is completely outside the visible page area", ) } else { null } }}Treat these issues as errors because the exported output does not include the content.
Detecting Protruding Elements#
Elements that extend beyond the page boundaries get cropped on export. Reuse the same block scan and report blocks whose overlap ratio is greater than 0 but less than 1.
private fun findProtrudingBlocks( engine: Engine, page: DesignBlock,): List<ValidationIssue> { val pageBounds = boundingBox(engine = engine, block = page) val candidates = validationCandidates(engine = engine, page = page)
return candidates.mapNotNull { block -> val blockBounds = boundingBox(engine = engine, block = block) val overlap = overlapRatio(blockBounds, pageBounds) if (overlap > 0F && overlap < 0.99F) { ValidationIssue( kind = ValidationIssueKind.PROTRUDING, severity = ValidationSeverity.WARNING, block = block, blockName = displayName(engine = engine, block = block), message = "Element extends beyond page boundaries", ) } else { null } }}The sample uses a small tolerance (< 0.99) to avoid false positives from sub-pixel rounding. These issues are warnings because the content is still partially visible.
Finding Obscured Text#
Text hidden behind other elements can be unreadable in the final export. Recursively walking getChildren() gives a render-order list, so blocks later in the list render above earlier blocks.
private fun findObscuredText( engine: Engine, page: DesignBlock,): List<ValidationIssue> { val blocksInRenderOrder = pageDescendantsInRenderOrder(engine = engine, parent = page) val textBlocks = blocksInRenderOrder.filter { block -> engine.block.isValid(block) && engine.block.isVisible(block) && engine.block.isIncludedInExport(block) && engine.block.getType(block) == DesignBlockType.Text.key }
return textBlocks.mapNotNull { textBlock -> val textIndex = blocksInRenderOrder.indexOf(textBlock) if (textIndex == -1) return@mapNotNull null
val textBounds = boundingBox(engine = engine, block = textBlock) val isObscured = blocksInRenderOrder.drop(textIndex + 1).any { blockAbove -> canObscureText(engine = engine, block = blockAbove) && overlapRatio(textBounds, boundingBox(engine = engine, block = blockAbove)) > 0F }
if (isObscured) { ValidationIssue( kind = ValidationIssueKind.TEXT_OBSCURED, severity = ValidationSeverity.WARNING, block = textBlock, blockName = displayName(engine = engine, block = textBlock), message = "Text may be partially hidden by overlapping elements", ) } else { null } }}
private fun canObscureText( engine: Engine, block: DesignBlock,): Boolean { if (!engine.block.isValid(block)) return false
val blockType = engine.block.getType(block) if (blockType == DesignBlockType.Text.key || blockType == DesignBlockType.Group.key) return false
return engine.block.isVisible(block) && engine.block.isIncludedInExport(block)}For each text block, the check looks only at blocks above it and skips text-on-text overlaps. Group containers are skipped because their children are checked individually. When a rendered child block overlaps the text bounds, the text receives a warning.
Checking Placeholder Content#
Placeholders mark areas the user must fill before export. findAllPlaceholders() returns configured placeholder blocks, isPlaceholderEnabled() confirms that the placeholder function is still enabled, and the helper scopes those blocks to the page being exported.
private fun findUnfilledPlaceholders( engine: Engine, page: DesignBlock,): List<ValidationIssue> { val pageBlocks = pageDescendantsInRenderOrder(engine = engine, parent = page).toSet() return engine.block.findAllPlaceholders().filter { it in pageBlocks }.mapNotNull { placeholder -> if (!engine.block.isValid(placeholder)) return@mapNotNull null if (!engine.block.isPlaceholderEnabled(placeholder)) return@mapNotNull null
if (!isPlaceholderFilled(engine = engine, block = placeholder)) { ValidationIssue( kind = ValidationIssueKind.UNFILLED_PLACEHOLDER, severity = ValidationSeverity.ERROR, block = placeholder, blockName = displayName(engine = engine, block = placeholder), message = "Placeholder has not been filled with content", ) } else { null } }}
private fun isPlaceholderFilled( engine: Engine, block: DesignBlock,): Boolean { if (!engine.block.supportsFill(block)) return false
val fill = engine.block.getFill(block) if (!engine.block.isValid(fill)) return false if (engine.block.getType(fill) != FillType.Image.key) return true
return engine.block.getUri( block = fill, property = "fill/image/imageFileURI", ).toString().isNotBlank()}Unfilled placeholders are errors because they indicate incomplete design content. The sample uses placeholder state to find configured placeholders, then inspects the placeholder fill. Image placeholders are considered filled only when their image fill has a non-empty fill/image/imageFileURI value; other valid fill types are already filled content.
Running Validation#
Aggregate the four checks into one ValidationResult, then block export when result.errors is not empty. Selecting the first error block helps users locate the issue before trying again.
private fun validateDesign( engine: Engine, page: DesignBlock,): ValidationResult { val allIssues = findOutsideBlocks(engine = engine, page = page) + findProtrudingBlocks(engine = engine, page = page) + findObscuredText(engine = engine, page = page) + findUnfilledPlaceholders(engine = engine, page = page)
val result = ValidationResult( errors = allIssues.filter { it.severity == ValidationSeverity.ERROR }, warnings = allIssues.filter { it.severity == ValidationSeverity.WARNING }, )
result.errors.firstOrNull()?.block?.takeIf(engine.block::isValid)?.let { firstError -> engine.block.select(firstError) }
return result}Use warnings for confirmation or logging before you continue with export.
API Reference#
| Method | Purpose |
|---|---|
engine.block.getGlobalBoundingBoxX(block=_) |
Get the block’s global X position |
engine.block.getGlobalBoundingBoxY(block=_) |
Get the block’s global Y position |
engine.block.getGlobalBoundingBoxWidth(block=_) |
Get the block’s global width |
engine.block.getGlobalBoundingBoxHeight(block=_) |
Get the block’s global height |
engine.block.getChildren(block=_) |
Get child blocks in rendering order |
engine.block.getType(block=_) |
Read the block type string |
engine.block.getKind(block=_) |
Read the block kind string |
engine.block.getName(block=_) |
Read the block display name |
engine.block.isValid(block=_) |
Check whether the block still exists |
engine.block.isVisible(block=_) |
Check whether a block is visible |
engine.block.isIncludedInExport(block=_) |
Check whether a block is exported |
engine.block.findAllPlaceholders() |
Find all placeholder blocks in the scene |
engine.block.isPlaceholderEnabled(block=_) |
Check whether a block is still marked as a placeholder |
engine.block.supportsFill(block=_) |
Check whether a block can have a fill |
engine.block.getFill(block=_) |
Get the block’s fill block |
engine.block.getUri(block=_, property="fill/image/imageFileURI") |
Read the image fill source URI |
engine.block.select(block=_) |
Select the first block that needs attention |
Next Steps#
- Export Overview — Learn about the available export formats and options
- Blocks — Understand the block hierarchy and positioning model