Skip to content
Merged
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 @@ -52,7 +52,7 @@ class OrderedListSpan(

override fun getMarkerWidth(): Float {
val paint = configureMarkerPaint()
return paint.measureText("99.")
return listStyle.effectiveMarkerWidth(paint.measureText("99."))
}

var itemNumber: Int = 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import com.swmansion.enriched.markdown.styles.TaskListStyle

class TaskListSpan(
private val taskStyle: TaskListStyle,
listStyle: ListStyle,
private val listStyle: ListStyle,
depth: Int,
context: Context,
styleCache: SpanStyleCache,
Expand All @@ -34,6 +34,7 @@ class TaskListSpan(
gapWidth = listStyle.gapWidth,
) {
private val checkboxSize = taskStyle.checkboxSize
private val markerColumnWidth = listStyle.effectiveMarkerWidth(checkboxSize)
private val cornerRadius = taskStyle.checkboxBorderRadius
private val rect = RectF()
private val checkPath = Path()
Expand All @@ -55,7 +56,7 @@ class TaskListSpan(
strokeJoin = Paint.Join.ROUND
}

override fun getMarkerWidth(): Float = checkboxSize
override fun getMarkerWidth(): Float = markerColumnWidth

override fun drawMarker(
canvas: Canvas,
Expand All @@ -71,7 +72,10 @@ class TaskListSpan(
val fontMetrics = paint.fontMetrics
val capHeight = -fontMetrics.ascent * CAP_HEIGHT_RATIO
val centerY = baseline - capHeight / HALF_DIVISOR
val centerX = x + (depth * marginLeft + checkboxSize / HALF_DIVISOR) * dir
// Right-align the checkbox inside the reserved marker column so it hugs
// the gap before the text. At the default (markerColumnWidth ==
// checkboxSize) this is identical to the previous flush-left layout.
val centerX = x + (depth * marginLeft + markerColumnWidth - checkboxSize / HALF_DIVISOR) * dir
val half = checkboxSize / HALF_DIVISOR
rect.set(centerX - half, centerY - half, centerX + half, centerY + half)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,14 @@ class UnorderedListSpan(
}

private val radius: Float = listStyle.bulletSize / 2f
private val markerColumnWidth: Float = listStyle.effectiveMarkerWidth(radius)

private fun configureBulletPaint(): Paint =
sharedBulletPaint.apply {
color = listStyle.bulletColor
}

override fun getMarkerWidth(): Float = radius
override fun getMarkerWidth(): Float = markerColumnWidth

override fun drawMarker(
canvas: Canvas,
Expand All @@ -55,7 +56,11 @@ class UnorderedListSpan(
start: Int,
) {
val bulletPaint = configureBulletPaint()
val bulletX = x + (depth * marginLeft + radius) * dir
// Center the bullet at the right edge of the reserved marker column so
// it hugs the gap before the text — matches iOS behavior and stays
// visually flush-left when the column width equals the bullet radius
// (the default).
val bulletX = x + (depth * marginLeft + markerColumnWidth) * dir
val fontMetrics = paint.fontMetrics
val bulletY = baseline + (fontMetrics.ascent + fontMetrics.descent) / 2f

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@ data class ListStyle(
override val lineHeight: Float,
val bulletColor: Int,
val bulletSize: Float,
val markerMinWidth: Float,
val markerColor: Int,
val markerFontWeight: String,
val gapWidth: Float,
val marginLeft: Float,
) : BaseBlockStyle {
fun effectiveMarkerWidth(naturalWidth: Float): Float = naturalWidth.coerceAtLeast(markerMinWidth)

companion object {
fun fromReadableMap(
map: ReadableMap,
Expand All @@ -32,6 +35,7 @@ data class ListStyle(
val lineHeight = parser.toPixelFromSP(lineHeightRaw)
val bulletColor = parser.parseColor(map, "bulletColor")
val bulletSize = parser.toPixelFromDIP(map.getDouble("bulletSize").toFloat())
val markerMinWidth = parser.toPixelFromDIP(map.getDouble("markerMinWidth").toFloat().coerceAtLeast(0f))
val markerColor = parser.parseColor(map, "markerColor")
val markerFontWeight = parser.parseString(map, "markerFontWeight", "normal")
val gapWidth = parser.toPixelFromDIP(map.getDouble("gapWidth").toFloat())
Expand All @@ -47,6 +51,7 @@ data class ListStyle(
lineHeight,
bulletColor,
bulletSize,
markerMinWidth,
markerColor,
markerFontWeight,
gapWidth,
Expand Down
1 change: 1 addition & 0 deletions apps/example/src/markdownStyles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ export const customMarkdownStyle: MarkdownStyle = {
lineHeight: Platform.select({ ios: 22, android: 26, default: 26 }),
bulletColor: '#6b7280',
bulletSize: 6,
markerMinWidth: 20,
markerColor: '#6b7280',
markerFontWeight: '500',
gapWidth: 8,
Expand Down
1 change: 1 addition & 0 deletions docs/STYLES.md
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,7 @@ The library provides sensible default styles for all Markdown elements out of th
|----------|------|-------------|
| `bulletColor` | `string` | Bullet point color |
| `bulletSize` | `number` | Bullet point size |
| `markerMinWidth` | `number` | Minimum reserved marker column width (floors the natural width of every list type) |
| `markerColor` | `string` | Number marker color |
| `markerFontWeight` | `string` | Number marker font weight |
| `gapWidth` | `number` | Gap between marker and text |
Expand Down
2 changes: 1 addition & 1 deletion ios/internals/MeasurementCache.h
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ template <typename StyleStruct> inline size_t computeStyleFingerprint(const Styl
hashFields(s.blockquote.borderWidth, s.blockquote.gapWidth);

hashTextLayout(s.list);
hashFields(s.list.bulletSize, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft);
hashFields(s.list.bulletSize, s.list.markerMinWidth, s.list.markerFontWeight, s.list.gapWidth, s.list.marginLeft);

// Code & Inlines
hashFields(s.codeBlock.fontFamily, s.codeBlock.fontSize, s.codeBlock.fontWeight, s.codeBlock.marginTop,
Expand Down
2 changes: 1 addition & 1 deletion ios/renderer/ListItemRenderer.m
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ - (void)renderNode:(MarkdownASTNode *)node into:(NSMutableAttributedString *)out

// currentDepth - 1 handles the horizontal offset for nested lists
const NSInteger nestingLevel = currentDepth - 1;
const CGFloat baseMarkerWidth = isTask ? [_config taskListCheckboxSize]
const CGFloat baseMarkerWidth = isTask ? [_config effectiveListMarginLeftForTask]
: (context.listType == ListTypeOrdered) ? [_config effectiveListMarginLeftForNumber]
: [_config effectiveListMarginLeftForBullet];

Expand Down
3 changes: 3 additions & 0 deletions ios/styles/StyleConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@
- (void)setListStyleBulletColor:(RCTUIColor *)newValue;
- (CGFloat)listStyleBulletSize;
- (void)setListStyleBulletSize:(CGFloat)newValue;
- (CGFloat)listStyleMarkerMinWidth;
- (void)setListStyleMarkerMinWidth:(CGFloat)newValue;
- (RCTUIColor *)listStyleMarkerColor;
- (void)setListStyleMarkerColor:(RCTUIColor *)newValue;
- (NSString *)listStyleMarkerFontWeight;
Expand All @@ -249,6 +251,7 @@
- (CGFloat)effectiveListGapWidth;
- (CGFloat)effectiveListMarginLeftForBullet;
- (CGFloat)effectiveListMarginLeftForNumber;
- (CGFloat)effectiveListMarginLeftForTask;
// Code block properties
- (CGFloat)codeBlockFontSize;
- (void)setCodeBlockFontSize:(CGFloat)newValue;
Expand Down
24 changes: 20 additions & 4 deletions ios/styles/StyleConfig.mm
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ @implementation StyleConfig {
CGFloat _listStyleLineHeight;
RCTUIColor *_listStyleBulletColor;
CGFloat _listStyleBulletSize;
CGFloat _listStyleMarkerMinWidth;
RCTUIColor *_listStyleMarkerColor;
NSString *_listStyleMarkerFontWeight;
CGFloat _listStyleGapWidth;
Expand Down Expand Up @@ -434,6 +435,7 @@ - (id)copyWithZone:(NSZone *)zone
copy->_listStyleLineHeight = _listStyleLineHeight;
copy->_listStyleBulletColor = [_listStyleBulletColor copy];
copy->_listStyleBulletSize = _listStyleBulletSize;
copy->_listStyleMarkerMinWidth = _listStyleMarkerMinWidth;
copy->_listStyleMarkerColor = [_listStyleMarkerColor copy];
copy->_listStyleMarkerFontWeight = [_listStyleMarkerFontWeight copy];
copy->_listStyleGapWidth = _listStyleGapWidth;
Expand Down Expand Up @@ -1706,6 +1708,16 @@ - (void)setListStyleBulletSize:(CGFloat)newValue
_listStyleBulletSize = newValue;
}

- (CGFloat)listStyleMarkerMinWidth
{
return _listStyleMarkerMinWidth;
}

- (void)setListStyleMarkerMinWidth:(CGFloat)newValue
{
_listStyleMarkerMinWidth = newValue;
}

- (RCTUIColor *)listStyleMarkerColor
{
return _listStyleMarkerColor;
Expand Down Expand Up @@ -1786,16 +1798,20 @@ - (CGFloat)effectiveListGapWidth

- (CGFloat)effectiveListMarginLeftForBullet
{
// Just the minimum width needed for bullet (radius)
return _listStyleBulletSize / 2.0;
return MAX(_listStyleMarkerMinWidth, _listStyleBulletSize / 2.0);
}

- (CGFloat)effectiveListMarginLeftForNumber
{
// Reserve width for numbers up to 99 (matching Android)
UIFont *font = [self listMarkerFont];
return
CGFloat natural =
[@"99." sizeWithAttributes:@{NSFontAttributeName : font ?: [UIFont systemFontOfSize:_listStyleFontSize]}].width;
return MAX(_listStyleMarkerMinWidth, natural);
}

- (CGFloat)effectiveListMarginLeftForTask
{
return MAX(_listStyleMarkerMinWidth, [self taskListCheckboxSize]);
}

// Code block properties
Expand Down
5 changes: 5 additions & 0 deletions ios/utils/StylePropsUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -713,6 +713,11 @@ BOOL applyMarkdownStyleToConfig(StyleConfig *config, const MarkdownStyle &newSty
changed = YES;
}

if (newStyle.list.markerMinWidth != oldStyle.list.markerMinWidth) {
[config setListStyleMarkerMinWidth:newStyle.list.markerMinWidth];
changed = YES;
}

if (newStyle.list.markerColor != oldStyle.list.markerColor) {
RCTUIColor *markerColor = RCTUIColorFromSharedColor(newStyle.list.markerColor);
[config setListStyleMarkerColor:markerColor];
Expand Down
1 change: 1 addition & 0 deletions src/EnrichedMarkdownNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal {
interface ListStyleInternal extends BaseBlockStyleInternal {
bulletColor: ColorValue;
bulletSize: CodegenTypes.Float;
markerMinWidth: CodegenTypes.Float;
markerColor: ColorValue;
markerFontWeight: string;
gapWidth: CodegenTypes.Float;
Expand Down
1 change: 1 addition & 0 deletions src/EnrichedMarkdownTextNativeComponent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal {
interface ListStyleInternal extends BaseBlockStyleInternal {
bulletColor: ColorValue;
bulletSize: CodegenTypes.Float;
markerMinWidth: CodegenTypes.Float;
markerColor: ColorValue;
markerFontWeight: string;
gapWidth: CodegenTypes.Float;
Expand Down
1 change: 1 addition & 0 deletions src/normalizeMarkdownStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ const DEFAULT_NORMALIZED_STYLE = Object.freeze({
marginBottom: 16,
bulletColor: normalizeColor('#6B7280')!,
bulletSize: 6,
markerMinWidth: 0,
markerColor: normalizeColor('#6B7280')!,
markerFontWeight: '500',
gapWidth: 12,
Expand Down
1 change: 1 addition & 0 deletions src/normalizeMarkdownStyle.web.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ const DEFAULT_NORMALIZED_STYLE: MarkdownStyleInternal = Object.freeze({
marginBottom: 16,
bulletColor: '#6B7280',
bulletSize: 6,
markerMinWidth: 0,
markerColor: '#6B7280',
markerFontWeight: '500',
gapWidth: 12,
Expand Down
5 changes: 5 additions & 0 deletions src/types/MarkdownStyle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ interface BlockquoteStyle extends BaseBlockStyle {
interface ListStyle extends BaseBlockStyle {
bulletColor?: string;
bulletSize?: number;
/**
* Minimum reserved marker column width applied uniformly to UL/OL/task lists.
* `0` (the default) means no minimum — each list uses its natural marker width.
*/
markerMinWidth?: number;
markerColor?: string;
markerFontWeight?: string;
gapWidth?: number;
Expand Down
1 change: 1 addition & 0 deletions src/types/MarkdownStyleInternal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ interface BlockquoteStyleInternal extends BaseBlockStyleInternal {
interface ListStyleInternal extends BaseBlockStyleInternal {
bulletColor: string;
bulletSize: number;
markerMinWidth: number;
markerColor: string;
markerFontWeight: string;
gapWidth: number;
Expand Down
Loading