简体   繁体   English

如何在NSTextView中统一缩放富文本?

[英]How to uniformly scale rich text in an NSTextView?

Context : 背景

I have a normal Document-based Cocoa Mac OS X application which uses an NSTextView for rich text input. 我有一个普通的基于文档的Cocoa Mac OS X应用程序,它使用NSTextView进行富文本输入。 The user may edit the font family, point size and colors of the text in the NSTextView . 用户可以编辑NSTextView本的字体系列,点大小和颜色。

Base SDK: 10.7 基础SDK:10.7
Deployment Target: 10.6 部署目标:10.6


Question: 题:

I would like to implement zooming of the entire UI programmatically (including the NSTextView ) while the user is editing text. 我想在用户编辑文本时以编程方式(包括NSTextView )实现整个UI的缩放。 Scaling the frame of the NSTextView is no problem. 缩放NSTextView的框架是没有问题的。 But I don't know how to scale the editable text inside the view which may contain multiple different point sizes in different sub-sections of the entire run of text. 但我不知道如何缩放视图中的可编辑文本,该文本可能在整个文本运行的不同子部分中包含多个不同的点大小。

How can I apply a uniform scale factor to the rich text displayed in an NSTextView ? 如何将统一比例因子应用于NSTextView显示的富文本?

This should play nicely with "rich text", such that the user's font family, color and especially point size (which may be different at different points of the run of text) are preserved, but scaled uniformly/relatively. 这应该与“富文本”很好地配合,使得用户的字体系列,颜色和特别是点大小 (在文本运行的不同点处可能不同)被保留,但是均匀/相对地缩放。

Is this possible given my Base SDK and Deployment targets? 这可能是我的Base SDK和部署目标吗? Is it possible with a newer Base SDK or Deployment target? 是否可以使用较新的Base SDK或部署目标?

If the intent is to scale the view (and not actually change the attributes in the string), I would suggest using scaleUnitSquareToSize: method: along with the ScalingScrollView (available with the TextEdit sample code) for the proper scroll bar behavior. 如果目的是缩放视图(而不是实际更改字符串中的属性),我建议使用scaleUnitSquareToSize:method:以及ScalingScrollView(可与TextEdit示例代码一起使用)以获得正确的滚动条行为。

The core piece from the ScalingScrollView is: ScalingScrollView的核心部分是:

- (void)setScaleFactor:(CGFloat)newScaleFactor adjustPopup:(BOOL)flag
{
CGFloat oldScaleFactor = scaleFactor;
    if (scaleFactor != newScaleFactor)
    {
        NSSize curDocFrameSize, newDocBoundsSize;
        NSView *clipView = [[self documentView] superview];

        scaleFactor = newScaleFactor;

        // Get the frame.  The frame must stay the same.
        curDocFrameSize = [clipView frame].size;

        // The new bounds will be frame divided by scale factor
        newDocBoundsSize.width = curDocFrameSize.width / scaleFactor;
        newDocBoundsSize.height = curDocFrameSize.height / scaleFactor;
    }
    scaleFactor = newScaleFactor;
    [scale_delegate scaleChanged:oldScaleFactor newScale:newScaleFactor]; 
}

The scale_delegate is your delegate that can adjust your NSTextView object: scale_delegate是您可以调整NSTextView对象的委托:

- (void) scaleChanged:(CGFloat)oldScale newScale:(CGFloat)newScale
{
    NSInteger     percent  = lroundf(newScale * 100);

    CGFloat scaler = newScale / oldScale;   
    [textView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)];

    NSLayoutManager* lm = [textView layoutManager];
    NSTextContainer* tc = [textView textContainer];
    [lm ensureLayoutForTextContainer:tc];
}

The scaleUnitSquareToSize: method scales relative to its current state, so you keep track of your scale factor and then convert your absolute scale request (200%) into a relative scale request. scaleUnitSquareToSize:方法相对于其当前状态进行缩放,因此您可以跟踪比例因子,然后将绝对比例请求(200%)转换为相对比例请求。

Works for both iOS and Mac OS 适用于iOS和Mac OS

@implementation NSAttributedString (Scale)

- (NSAttributedString *)attributedStringWithScale:(double)scale
{
    if(scale == 1.0)
    {
        return self;
    }

    NSMutableAttributedString *copy = [self mutableCopy];
    [copy beginEditing];

    NSRange fullRange = NSMakeRange(0, copy.length);

    [self enumerateAttribute:NSFontAttributeName inRange:fullRange options:0 usingBlock:^(UIFont *oldFont, NSRange range, BOOL *stop) {
        double currentFontSize = oldFont.pointSize;
        double newFontSize = currentFontSize * scale;

        // don't trust -[UIFont fontWithSize:]
        UIFont *scaledFont = [UIFont fontWithName:oldFont.fontName size:newFontSize];

        [copy removeAttribute:NSFontAttributeName range:range];
        [copy addAttribute:NSFontAttributeName value:scaledFont range:range];
    }];

    [self enumerateAttribute:NSParagraphStyleAttributeName inRange:fullRange options:0 usingBlock:^(NSParagraphStyle *oldParagraphStyle, NSRange range, BOOL *stop) {

        NSMutableParagraphStyle *newParagraphStyle = [oldParagraphStyle mutableCopy];
        newParagraphStyle.lineSpacing *= scale;
        newParagraphStyle.paragraphSpacing *= scale;
        newParagraphStyle.firstLineHeadIndent *= scale;
        newParagraphStyle.headIndent *= scale;
        newParagraphStyle.tailIndent *= scale;
        newParagraphStyle.minimumLineHeight *= scale;
        newParagraphStyle.maximumLineHeight *= scale;
        newParagraphStyle.paragraphSpacing *= scale;
        newParagraphStyle.paragraphSpacingBefore *= scale;

        [copy removeAttribute:NSParagraphStyleAttributeName range:range];
        [copy addAttribute:NSParagraphStyleAttributeName value:newParagraphStyle range:range];
    }];

    [copy endEditing];
    return copy;
}

@end

OP here. OP在这里。

I found one solution that kinda works and is not terribly difficult to implement. 我找到了一种有效的解决方案,并且实施起来并不十分困难。 I'm not sure this is the best/ideal solution however. 我不确定这是最好/理想的解决方案。 I'm still interested in finding other solutions. 我仍然有兴趣寻找其他解决方案。 But here's one way: 但这是一种方式:

Manually scale the font point size and line height multiple properties of the NSAttributedString source text before display, and then un-scale the displayed text before storing as source. 在显示之前手动缩放NSAttributedString源文本的字体 NSAttributedString 行高度多个属性,然后在存储为源之前取消缩放显示的文本。

The problem with this solution is that while scaled, the system Font Panel will show the actual scaled display point size of selected text (rather than the "real" source point size) while editing. 此解决方案的问题在于,在缩放时,系统字体面板将在编辑时显示所选文本的实际缩放显示点大小(而不是“实际”源点大小)。 That's not desirable. 那是不可取的。


Here's my implementation of that: 这是我的实现:

- (void)scaleAttributedString:(NSMutableAttributedString *)str by:(CGFloat)scale {
    if (1.0 == scale) return;

    NSRange r = NSMakeRange(0, [str length]);
    [str enumerateAttribute:NSFontAttributeName inRange:r options:0 usingBlock:^(NSFont *oldFont, NSRange range, BOOL *stop) {
        NSFont *newFont = [NSFont fontWithName:[oldFont familyName] size:[oldFont pointSize] * scale];

        NSParagraphStyle *oldParaStyle = [str attribute:NSParagraphStyleAttributeName atIndex:range.location effectiveRange:NULL];
        NSMutableParagraphStyle *newParaStyle = [[oldParaStyle mutableCopy] autorelease];

        CGFloat oldLineHeight = [oldParaStyle lineHeightMultiple];
        CGFloat newLineHeight = scale * oldLineHeight;
        [newParaStyle setLineHeightMultiple:newLineHeight];

        id newAttrs = @{
            NSParagraphStyleAttributeName: newParaStyle,
            NSFontAttributeName: newFont,
        };
        [str addAttributes:newAttrs range:range];
    }];    
}

This requires scaling the source text before display: 这需要在显示之前缩放源文本:

// scale text
CGFloat scale = getCurrentScaleFactor();
[self scaleAttributedString:str by:scale];

And then reverse-scaling the displayed text before storing as source: 然后在存储为源之前反向缩放显示的文本:

// un-scale text
CGFloat scale = 1.0 / getCurrentScaleFactor();
[self scaleAttributedString:str by:scale];

I want to thank Mark Munz for his answer, as it saved me from wandering in a dark forest, full of of NSScrollView magnification madness and NSLayoutManagers. 我要感谢Mark Munz的答案,因为它让我免于在黑暗的森林中游荡,充满了NSScrollView放大率疯狂和NSLayoutManagers。

For anyone still looking, this is my approach. 对于仍在寻找的人来说,这是我的方法。 This code is inside a NSDocument. 此代码位于NSDocument中。 All text is being inset into a fixed-width and centered container, and the zooming here keeps word wrapping etc. intact. 所有文本都嵌入到固定宽度和居中的容器中,这里的缩放使自动换行等完整。 It creates a nice "page view" sort of appearance without resorting to complicated layout management. 它创建了一个漂亮的“页面视图”类型的外观,而无需借助复杂的布局管理。

You need to have CGFloat _documentSize and NSTextView textView constants set in you class for this example to work. 您需要在您的类中设置CGFloat _documentSizeNSTextView textView常量才能使用此示例。

- (void) initZoom {
    // Call this when the view has loaded and is ready
    // I am storing a separate _scaleFactor and _magnification for my own purposes, mainly to have the initial scale to be higher than 1.0
    _scaleFactor = 1.0;
    _magnification = 1.1;
    [self setScaleFactor:_magnification adjustPopup:false];

    [self updateLayout];
    // NOTE: You might need to call updateLayout after the content is set and we know the window size etc.
}

- (void) zoom: (bool) zoomIn {
    if (!_scaleFactor) _scaleFactor = _magnification;

    // Arbitrary maximum levels of zoom
    if (zoomIn) {
        if (_magnification < 1.6) _magnification += 0.1;
    } else {
        if (_magnification > 0.8) _magnification -= 0.1;
    }

    [self setScaleFactor:_magnification adjustPopup:false];
    [self updateLayout];
}

- (void)setScaleFactor:(CGFloat)newScaleFactor adjustPopup:(BOOL)flag
{
    CGFloat oldScaleFactor = _scaleFactor;
    if (_scaleFactor != newScaleFactor)
    {
        NSSize curDocFrameSize, newDocBoundsSize;
        NSView *clipView = [[self textView] superview];

        _scaleFactor = newScaleFactor;

        // Get the frame.  The frame must stay the same.
        curDocFrameSize = [clipView frame].size;

        // The new bounds will be frame divided by scale factor
        //newDocBoundsSize.width = curDocFrameSize.width / _scaleFactor;
        newDocBoundsSize.width = curDocFrameSize.width;
        newDocBoundsSize.height = curDocFrameSize.height / _scaleFactor;

        NSRect newFrame = NSMakeRect(0, 0, newDocBoundsSize.width, newDocBoundsSize.height);
        clipView.frame = newFrame;
    }
    _scaleFactor = newScaleFactor;
    [self scaleChanged:oldScaleFactor newScale:newScaleFactor];
}
- (void) scaleChanged:(CGFloat)oldScale newScale:(CGFloat)newScale
{
    CGFloat scaler = newScale / oldScale;
    [self.textView scaleUnitSquareToSize:NSMakeSize(scaler, scaler)];

    NSLayoutManager* lm = [self.textView layoutManager];
    NSTextContainer* tc = [self.textView textContainer];
    [lm ensureLayoutForTextContainer:tc];
}

- (void) updateLayout {
    CGFloat width = (self.textView.frame.size.width / 2 - _documentWidth * _magnification / 2) / _magnification;    self.textView.textContainerInset = NSMakeSize(width, TEXT_INSET_TOP);
    self.textView.textContainer.size = NSMakeSize(_documentWidth, self.textView.textContainer.size.height);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM