Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

try fix 41908 #382

Closed
wants to merge 1 commit into from
Closed
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 @@ -3,50 +3,59 @@
import android.graphics.Canvas;
import android.graphics.Paint;
import android.text.Layout;
import android.text.style.LeadingMarginSpan;
import android.text.style.ReplacementSpan;

import androidx.annotation.ColorInt;

import com.facebook.react.uimanager.PixelUtil;

public class MarkdownBlockquoteSpan implements MarkdownSpan, LeadingMarginSpan {
public class MarkdownBlockquoteSpan extends ReplacementSpan implements MarkdownSpan {
@ColorInt
private final int syntaxColor;
@ColorInt
private final int borderColor;
private final float borderWidth;
private final float marginLeft;
private final float paddingLeft;
private final int nestingLevel;
private final int leadingMargin;

public MarkdownBlockquoteSpan(@ColorInt int borderColor, float borderWidth, float marginLeft, float paddingLeft, int nestingLevel) {
public MarkdownBlockquoteSpan(@ColorInt int syntaxColor, @ColorInt int borderColor, float borderWidth, float marginLeft, float paddingLeft, int nestingLevel) {
this.syntaxColor = syntaxColor;
this.borderColor = borderColor;
this.borderWidth = PixelUtil.toPixelFromDIP(borderWidth);
this.marginLeft = PixelUtil.toPixelFromDIP(marginLeft);
this.paddingLeft = PixelUtil.toPixelFromDIP(paddingLeft);
this.nestingLevel = nestingLevel;
this.leadingMargin = (int) (this.marginLeft + this.borderWidth + this.paddingLeft) * nestingLevel;
}

@Override
public int getLeadingMargin(boolean first) {
return (int) (marginLeft + borderWidth + paddingLeft) * nestingLevel;
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
int textWidth = Math.round(paint.measureText(text, start, end));
return textWidth + leadingMargin;
}

@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
CharSequence text, int start, int end, boolean first, Layout layout) {
Paint.Style originalStyle = p.getStyle();
int originalColor = p.getColor();
public void draw(Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, Paint paint) {
Paint.Style originalStyle = paint.getStyle();
int originalColor = paint.getColor();

paint.setColor(syntaxColor);
canvas.drawText(text, start, end, x + leadingMargin, y, paint);

p.setStyle(Paint.Style.FILL);
p.setColor(borderColor);
paint.setStyle(Paint.Style.FILL);
paint.setColor(borderColor);

for (int level = 0; level < nestingLevel; level++) {
float shift = (marginLeft + borderWidth + paddingLeft) * level;
float left = x + dir * (marginLeft + shift);
float right = x + dir * (marginLeft + borderWidth + shift);
c.drawRect(left, top, right, bottom, p);
float left = x + marginLeft + shift;
float right = x + marginLeft + borderWidth + shift;
canvas.drawRect(left, top, right, bottom, paint);
}

p.setStyle(originalStyle);
p.setColor(originalColor);
paint.setStyle(originalStyle);
paint.setColor(originalColor);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -159,12 +159,13 @@ private void applyRange(SpannableStringBuilder ssb, String type, int start, int
break;
case "blockquote":
MarkdownBlockquoteSpan span = new MarkdownBlockquoteSpan(
mMarkdownStyle.getSyntaxColor(),
mMarkdownStyle.getBlockquoteBorderColor(),
mMarkdownStyle.getBlockquoteBorderWidth(),
mMarkdownStyle.getBlockquoteMarginLeft(),
mMarkdownStyle.getBlockquotePaddingLeft(),
depth);
setSpan(ssb, span, start, end);
setSpan(ssb, span, start, start + 1);
break;
default:
throw new IllegalStateException("Unsupported type: " + type);
Expand Down
15 changes: 13 additions & 2 deletions ios/MarkdownLayoutManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,27 @@ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origi
[self enumerateLineFragmentsForGlyphRange:glyphsToShow usingBlock:^(CGRect rect, CGRect usedRect, NSTextContainer * _Nonnull textContainer, NSRange glyphRange, BOOL * _Nonnull stop) {
__block BOOL isBlockquote = NO;
__block int currentDepth = 0;
__block CGFloat lastLeadingWhiteSpaceOriginX = 0;
__block CGFloat lastLeadingWhiteSpaceWidthExceptKerning = 0;
RCTMarkdownUtils *markdownUtils = [self valueForKey:@"markdownUtils"];
[markdownUtils.blockquoteRangesAndLevels enumerateObjectsUsingBlock:^(NSDictionary *item, NSUInteger idx, BOOL * _Nonnull stop) {
NSRange range = [[item valueForKey:@"range"] rangeValue];
currentDepth = [[item valueForKey:@"depth"] unsignedIntegerValue];
NSUInteger leadingWhiteSpacesCountInFirstRangeLine = [[item valueForKey:@"leadingWhiteSpacesCountInFirstRangeLine"] unsignedIntegerValue];
NSUInteger start = range.location;
NSUInteger end = start + range.length;
NSUInteger location = glyphRange.location;
if (location >= start && location < end) {
if (location >= (start - leadingWhiteSpacesCountInFirstRangeLine) && location < end) {
isBlockquote = YES;
*stop = YES;
if (leadingWhiteSpacesCountInFirstRangeLine > 0) {
NSRange lastLeadingWhiteSpaceRange = NSMakeRange(start - 1, 1);
CGRect lastLeadingWhiteSpaceRect = [self boundingRectForGlyphRange:lastLeadingWhiteSpaceRange inTextContainer:textContainer];
CGFloat lastLeadingWhiteSpaceWidth = CGRectGetWidth(lastLeadingWhiteSpaceRect);
CGFloat lastLeadingWhiteSpaceKerning = (markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft) * currentDepth;
lastLeadingWhiteSpaceOriginX = lastLeadingWhiteSpaceRect.origin.x;
lastLeadingWhiteSpaceWidthExceptKerning = lastLeadingWhiteSpaceWidth - lastLeadingWhiteSpaceKerning;
}
}
}];
if (isBlockquote) {
Expand All @@ -28,7 +39,7 @@ - (void)drawBackgroundForGlyphRange:(NSRange)glyphsToShow atPoint:(CGPoint)origi
CGFloat height = rect.size.height;
CGFloat shift = markdownUtils.markdownStyle.blockquoteMarginLeft + markdownUtils.markdownStyle.blockquoteBorderWidth + markdownUtils.markdownStyle.blockquotePaddingLeft;
for (int level = 0; level < currentDepth; level++) {
CGFloat x = paddingLeft + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
CGFloat x = paddingLeft + lastLeadingWhiteSpaceOriginX + lastLeadingWhiteSpaceWidthExceptKerning + (level * shift) + markdownUtils.markdownStyle.blockquoteMarginLeft;
CGRect lineRect = CGRectMake(x, y, width, height);
[markdownUtils.markdownStyle.blockquoteBorderColor setFill];
UIRectFill(lineRect);
Expand Down
19 changes: 14 additions & 5 deletions ios/RCTMarkdownUtils.mm
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
NSInteger location = [[item valueForKey:@"start"] unsignedIntegerValue];
NSInteger length = [[item valueForKey:@"length"] unsignedIntegerValue];
NSInteger depth = [[item valueForKey:@"depth"] unsignedIntegerValue] ?: 1;
NSInteger leadingWhiteSpacesCountInFirstRangeLine = [[item valueForKey:@"leadingWhiteSpacesCountInFirstRangeLine"] unsignedIntegerValue] ?: 0;
NSRange range = NSMakeRange(location, length);

if ([type isEqualToString:@"bold"] || [type isEqualToString:@"italic"] || [type isEqualToString:@"code"] || [type isEqualToString:@"pre"] || [type isEqualToString:@"h1"] || [type isEqualToString:@"emoji"]) {
Expand Down Expand Up @@ -116,13 +117,21 @@ - (NSAttributedString *)parseMarkdown:(nullable NSAttributedString *)input withA
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.linkColor range:range];
} else if ([type isEqualToString:@"blockquote"]) {
CGFloat indent = (_markdownStyle.blockquoteMarginLeft + _markdownStyle.blockquoteBorderWidth + _markdownStyle.blockquotePaddingLeft) * depth;
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.firstLineHeadIndent = indent;
paragraphStyle.headIndent = indent;
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
if (leadingWhiteSpacesCountInFirstRangeLine == 0) {
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.firstLineHeadIndent = indent;
paragraphStyle.headIndent = indent;
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:range];
} else {
CGFloat kerningValue = indent;
NSRange targetRange = NSMakeRange(location - 1, 1);
[attributedString addAttribute:NSKernAttributeName value:@(kerningValue) range:targetRange];
}

[_blockquoteRangesAndLevels addObject:@{
@"range": [NSValue valueWithRange:range],
@"depth": @(depth)
@"depth": @(depth),
@"leadingWhiteSpacesCountInFirstRangeLine": @(leadingWhiteSpacesCountInFirstRangeLine)
}];
} else if ([type isEqualToString:@"pre"]) {
[attributedString addAttribute:NSForegroundColorAttributeName value:_markdownStyle.preColor range:range];
Expand Down
35 changes: 31 additions & 4 deletions parser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ type Range = {
start: number;
length: number;
depth?: number;
leadingWhiteSpacesCountInFirstRangeLine?: number;
};
type Token = ['TEXT' | 'HTML', string];
type StackItem = {tag: string; children: Array<StackItem | string>};
Expand Down Expand Up @@ -237,22 +238,48 @@ function groupRanges(ranges: Range[]) {
}, [] as Range[]);
}

function recoverPositionEffectedByTrim(ranges: Range[], markdown: string): Range[] {
const leadingWhiteSpacesAndLineTerminators = markdown.match(/^\s*/g)?.[0] || '';
return ranges.map((range, inx) => {
const start = range.start + leadingWhiteSpacesAndLineTerminators.length;
let leadingWhiteSpacesCountInFirstRangeLine: number | undefined;
if (inx === 0) {
let tryInx = start - 1;
while (tryInx >= 0) {
if (markdown[tryInx] === '\n' || markdown[tryInx] === '\r' || markdown[tryInx] === '\u2028' || markdown[tryInx] === '\u2029') {
break;
} else {
tryInx -= 1;
}
}
leadingWhiteSpacesCountInFirstRangeLine = start - tryInx - 1;
}
return {
...range,
start,
leadingWhiteSpacesCountInFirstRangeLine,
};
});
}

function parseExpensiMarkToRanges(markdown: string): Range[] {
try {
const html = parseMarkdownToHTML(markdown);
const trimmedMarkdown = markdown.trim();
const html = parseMarkdownToHTML(trimmedMarkdown);
const tokens = parseHTMLToTokens(html);
const tree = parseTokensToTree(tokens);
const [text, ranges] = parseTreeToTextAndRanges(tree);
if (text !== markdown) {
if (text !== trimmedMarkdown) {
throw new Error(
`[react-native-live-markdown] Parsing error: the processed text does not match the original Markdown input. This may be caused by incorrect parsing functions or invalid input Markdown.\nProcessed input: '${JSON.stringify(
text,
)}'\nOriginal input: '${JSON.stringify(markdown)}'`,
)}'\nOriginal input: '${JSON.stringify(trimmedMarkdown)}'`,
);
}
const sortedRanges = sortRanges(ranges);
const groupedRanges = groupRanges(sortedRanges);
return groupedRanges;
const recoveredRanges = recoverPositionEffectedByTrim(groupedRanges, markdown);
return recoveredRanges;
} catch (error) {
console.error(error);
// returning an empty array in case of error
Expand Down
Loading
Loading