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} /> +