I'm editing a subset of HTML in an NSTextView[1] and I want to simulate an <hr> tag.
I've figured out that the way to do it is with NSTextAttachment and a custom NSTextAttachmentCell, and have the code all written to insert the attachment and cell. The problem is, there's an enormous amount of blank space below the cell.
This space is not part of the cell itself—if I paint the entire area of the cell red, it's exactly the right size, but the text view is putting the next line of text very far below the red. The amount seems to depend on how much text is above the cell; unfortunately, I'm working with long documents where <hr> tags are crucial, and this causes major problems with the app.
What the heck is going on?
The money parts of my cell subclass:
- (NSRect)cellFrameForTextContainer:(NSTextContainer *)textContainer
proposedLineFragment:(NSRect)lineFrag glyphPosition:(NSPoint)position
characterIndex:(NSUInteger)charIndex {
lineFrag.size.width = textContainer.containerSize.width;
lineFrag.size.height = topMargin + TsStyleBaseFontSize *
heightFontSizeMultiplier + bottomMargin;
return lineFrag;
}
- (void)drawWithFrame:(NSRect)cellFrame inView:(NSView *)controlView
characterIndex:(NSUInteger)charIndex
layoutManager:(NSLayoutManager *)layoutManager {
NSRect frame = cellFrame;
frame.size.height -= bottomMargin;
frame.size.height -= topMargin;
frame.origin.y += topMargin;
frame.size.width *= widthPercentage;
frame.origin.x += (cellFrame.size.width - frame.size.width)/2;
[color set];
NSRectFill(frame);
}
[1] I tried a WebView with isEditable set and the markup it produced was unusably dirty—in particular, I couldn't find a way to wrap text nicely in <p> tags.
To answer Rob Keniger's request for the code that inserts the horizontal rule attachment:
- (void)insertHorizontalRule:(id)sender {
NSAttributedString * rule = [TsPage newHorizontalRuleAttributedStringWithStylebook:self.book.stylebook];
NSUInteger loc = self.textView.rangeForUserTextChange.location;
if(loc == NSNotFound) {
NSBeep();
return;
}
if(loc > 0 && [self.textView.textStorage.string characterAtIndex:loc - 1] != '\n') {
NSMutableAttributedString * workspace = rule.mutableCopy;
[workspace.mutableString insertString:@"\n" atIndex:0];
rule = workspace;
}
if([self.textView shouldChangeTextInRange:self.textView.rangeForUserTextChange replacementString:rule.string]) {
[self.textView.textStorage beginEditing];
[self.textView.textStorage replaceCharactersInRange:self.textView.rangeForUserTextChange withAttributedString:rule];
[self.textView.textStorage endEditing];
[self.textView didChangeText];
}
[self.textView scrollRangeToVisible:self.textView.rangeForUserTextChange];
[self reloadPreview:sender];
}
And the method in TsPage that constructs the attachment string:
+ (NSAttributedString *)newHorizontalRuleAttributedStringWithStylebook:(TsStylebook*)stylebook {
TsHorizontalRuleCell * cell = [[TsHorizontalRuleCell alloc] initTextCell:@"—"];
cell.widthPercentage = 0.33;
cell.heightFontSizeMultiplier = 0.25;
cell.topMargin = 12.0;
cell.bottomMargin = 12.0;
cell.color = [NSColor blackColor];
NSTextAttachment * attachment = [[NSTextAttachment alloc] initWithFileWrapper:nil];
attachment.attachmentCell = cell;
cell.attachment = attachment;
NSAttributedString * attachmentString = [NSAttributedString attributedStringWithAttachment:attachment];
NSMutableAttributedString * str = [[NSMutableAttributedString alloc] initWithString:@""];
[str appendAttributedString:attachmentString];
[str.mutableString appendString:@"\n"];
return str;
}
Try changing your cell class's cellFrameForTextContainer:proposedLineFragment:glyphPosition:
method to return NSZeroPoint
for the origin of the cell's frame.
(I have no idea why this should work, but the questioner and I have been live-debugging it, and it actually does.)
Added by questioner: It looks like, even though the rect being returned is described as a "frame", its origin is relative to the line it's in, not to the top of the document. Thus, the return value's origin.y
value should be set to zero if you want it on the same line.
(The origin.x
value, on the other hand, does refer to the cell's position in the line, so it should be the same as lineFrag.origin.x
unless you want to change the cell's horizontal location.)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With