Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
61 changes: 61 additions & 0 deletions example/src/ImageMarkerLab.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,58 @@ function useViewModel(props: ImageMarkerLabProps) {
);
}

async function runMultilineTextAlignmentFeature() {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disclaimer, this function has been autogenerated. The issue is reproduced without the fix (see the images in the PR description)

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(
Expand Down Expand Up @@ -1968,6 +2020,7 @@ function useViewModel(props: ImageMarkerLabProps) {
pickImage,
runMark,
runTextOffsetFeature,
runMultilineTextAlignmentFeature,
runImageOffsetFeature,
runMixedWatermarkFeature,
runBlendModeFeature,
Expand Down Expand Up @@ -2083,6 +2136,14 @@ function ImageMarkerLab(props: ImageMarkerLabProps) {
testID="feature-text-anchor-offset"
onPress={actions.runTextOffsetFeature}
/>
<FeatureCard
badge="Text"
title="Multiline text alignment"
meta="Compare left, center, and right at the same X"
tone="blue"
testID="feature-multiline-text-alignment"
onPress={actions.runMultilineTextAlignmentFeature}
/>
<FeatureCard
badge="Image"
title="Anchored image offset"
Expand Down
12 changes: 10 additions & 2 deletions ios/RCTImageMarker/TextLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,13 +47,21 @@ final class ImageMarkerTextLayout {

func draw(at origin: CGPoint) {
let glyphRange = layoutManager.glyphRange(for: textContainer)
let textBounds = layoutManager.usedRect(for: textContainer)

// Subtract textBounds.origin to counter the alignment offset applied by
// drawBackground and drawGlyphs.
let textContainerOrigin = CGPoint(
x: origin.x - textBounds.origin.x,
y: origin.y - textBounds.origin.y
)
layoutManager.drawBackground(
forGlyphRange: glyphRange,
at: origin
at: textContainerOrigin
)
layoutManager.drawGlyphs(
forGlyphRange: glyphRange,
at: origin
at: textContainerOrigin
)
}
}