diff --git a/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift b/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
index 873b9b87..effce81e 100644
--- a/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
+++ b/example/ios/ImageMarkerExampleUITests/ImageMarkerExampleUITests.swift
@@ -173,6 +173,48 @@ final class ImageMarkerExampleUITests: XCTestCase {
bytes[index] > 24 || bytes[index + 1] > 24 || bytes[index + 2] > 24
}
)
+
+ // Alignment must not move glyphs or their background away from the requested origin.
+ let alignmentStyle = try TextStyle(dicOpts: [:])
+ let font = UIFont.monospacedSystemFont(ofSize: 24, weight: .regular)
+ let format = UIGraphicsImageRendererFormat.default()
+ format.scale = 1
+ // Arbitrary nonzero coordinates verify that drawing respects the requested origin.
+ let origin = CGPoint(x: 13, y: 17)
+ for alignment in [NSTextAlignment.left, .center, .right] {
+ let paragraph = NSMutableParagraphStyle()
+ paragraph.alignment = alignment
+ let attributes: [NSAttributedString.Key: Any] = [
+ .font: font,
+ // Distinct colors let us check glyph and background positioning separately.
+ .foregroundColor: UIColor.blue,
+ .backgroundColor: UIColor.red,
+ .paragraphStyle: paragraph,
+ ]
+ let alignedLayout = ImageMarkerTextLayout(
+ text: NSAttributedString(string: "Hello\nWorld", attributes: attributes),
+ maxWidth: 200,
+ style: alignmentStyle
+ )
+ let image = UIGraphicsImageRenderer(
+ size: CGSize(width: 240, height: 80),
+ format: format
+ ).image { _ in
+ alignedLayout.draw(at: origin)
+ }
+ let bounds = try XCTUnwrap(bluePixelBounds(in: image))
+ // Visible glyphs must span more than one line.
+ XCTAssertGreaterThan(bounds.height, font.lineHeight)
+ XCTAssertEqual(bounds.minX, origin.x, accuracy: 0.001)
+ let pixels = try XCTUnwrap(rgbaBytes(for: image))
+ assertPixel(
+ pixels,
+ at: CGPoint(x: origin.x + 1, y: origin.y + 1),
+ width: Int(image.size.width),
+ matches: .red,
+ message: "Text background should start at the requested origin for alignment \(alignment.rawValue)"
+ )
+ }
}
func testCore21ImageInfoReadsEncodedOrientation() throws {
diff --git a/example/src/ImageMarkerLab.tsx b/example/src/ImageMarkerLab.tsx
index 2beb45c9..9bf1c981 100644
--- a/example/src/ImageMarkerLab.tsx
+++ b/example/src/ImageMarkerLab.tsx
@@ -797,6 +797,58 @@ function useViewModel(props: ImageMarkerLabProps) {
);
}
+ async function runMultilineTextAlignmentFeature() {
+ setLastRun('Multiline text alignment');
+ setUri('');
+ setShow(false);
+ setFileSize('0 B');
+ setResultContract('');
+ setLoading(true);
+
+ try {
+ const path = await resultUri(
+ Marker.markText({
+ backgroundImage: { src: assets.bg },
+ watermarkTexts: textAlignOptions.map((alignment, index) => ({
+ text: 'Hello\nWorld',
+ position: { X: '10%', Y: `${10 + index * 25}%` },
+ style: {
+ color: '#FFFFFF',
+ fontSize: 64,
+ lineHeight: 80,
+ textAlign: alignment,
+ maxWidth: '70%',
+ textBackgroundStyle: {
+ type: TextBackgroundType.none,
+ color: '#1E293B',
+ padding: 0,
+ },
+ },
+ })),
+ quality: 100,
+ saveFormat: ImageFormat.png,
+ })
+ );
+
+ setUri(formatResultUri(path, ImageFormat.png));
+ setShow(true);
+ setResultContract(
+ 'Top to bottom: left, center, right. Each row should show Hello and World ' +
+ 'inside its dark background. All three blocks should start at the same ' +
+ 'horizontal position, with no missing or clipped text. Tap the preview to enlarge.'
+ );
+ await updateFileSize(path, ImageFormat.png);
+ } catch (error) {
+ Toast.show({
+ type: 'error',
+ text1: 'multiline text alignment failed',
+ text2: error instanceof Error ? error.message : String(error),
+ });
+ } finally {
+ setLoading(false);
+ }
+ }
+
function runImageOffsetFeature() {
setBackgroundFormat('normal image');
runMark(
@@ -1968,6 +2020,7 @@ function useViewModel(props: ImageMarkerLabProps) {
pickImage,
runMark,
runTextOffsetFeature,
+ runMultilineTextAlignmentFeature,
runImageOffsetFeature,
runMixedWatermarkFeature,
runBlendModeFeature,
@@ -2083,6 +2136,14 @@ function ImageMarkerLab(props: ImageMarkerLabProps) {
testID="feature-text-anchor-offset"
onPress={actions.runTextOffsetFeature}
/>
+