<commit_before>import Foundation
import UIKit

protocol TextAttachmentImageProvider {
    func textAttachment(
        _ textAttachment: TextAttachment,
        imageForURL url: URL,
        onSuccess success: @escaping (UIImage) -> (),
        onFailure failure: @escaping () -> ()) -> UIImage
}

/// Custom text attachment.
///
open class TextAttachment: NSTextAttachment
{
    public struct Appearance {
        public var overlayColor = UIColor(white: 0.6, alpha: 0.6)

        /// The height of the progress bar for progress indicators
        public var progressHeight = CGFloat(2.0)

        /// The color to use when drawing the backkground of the progress indicators
        ///
        public var progressBackgroundColor = UIColor.cyan

        /// The color to use when drawing progress indicators
        ///
        public var progressColor = UIColor.blue

        public var imageMargin = CGFloat(10.0)
    }


    /// This property allows the global customization of appearance properties of the TextAttachment
    ///
    open static var appearance: Appearance = Appearance()

    /// Identifier used to match this attachment with a custom UIView subclass
    ///
    fileprivate(set) open var identifier: String
    
    /// Attachment URL
    ///
    open var url: URL?
    fileprivate var lastRequestedURL: URL?

    /// Attachment Alignment
    ///
    open var alignment: Alignment = .center {
        willSet {
            if newValue != alignment {
                glyphImage = nil
            }
        }
    }

    /// Attachment Size
    ///
    open var size: Size = .full {
        willSet {
            if newValue != size {
                glyphImage = nil
            }
        }
    }

    /// A progress value that indicates the progress of an attachment. It can be set between values 0 and 1
    ///
    open var progress: Double? = nil {
        willSet {
            assert(newValue == nil || (newValue! >= 0 && newValue! <= 1), "Progress must be value between 0 and 1 or nil")
            if newValue != progress {
                glyphImage = nil
            }
        }
    }

    /// The color to use when drawing the background overlay for messages, icons, and progress
    ///
    open var overlayColor: UIColor = TextAttachment.appearance.overlayColor

    /// The height of the progress bar for progress indicators
    open var progressHeight: CGFloat = TextAttachment.appearance.progressHeight

    /// The color to use when drawing the backkground of the progress indicators
    ///
    open var progressBackgroundColor: UIColor = TextAttachment.appearance.progressBackgroundColor

    /// The color to use when drawing progress indicators
    ///
    open var progressColor: UIColor = TextAttachment.appearance.progressColor

    open var imageMargin: CGFloat = TextAttachment.appearance.imageMargin

    /// A message to display overlaid on top of the image
    ///
    open var message: NSAttributedString? {
        willSet {
            if newValue != message {
                glyphImage = nil
            }
        }
    }

    /// An image to display overlaid on top of the image has an action icon.
    ///
    open var overlayImage: UIImage? {
        willSet {
            if newValue != overlayImage {
                glyphImage = nil
            }
        }
    }


    /// Clears all overlay information that is applied to the attachment
    ///
    open func clearAllOverlays() {
        progress = nil
        message = nil
        overlayImage = nil
    }

    fileprivate var glyphImage: UIImage?

    var imageProvider: TextAttachmentImageProvider?
    
    var isFetchingImage: Bool = false

    /// Creates a new attachment
    ///
    /// - parameter identifier: An unique identifier for the attachment
    ///
    /// - returns: self, initilized with the identifier a with kind = .MissingImage
    ///
    required public init(identifier: String, url: URL? = nil) {
        self.identifier = identifier
        self.url = url
        
        super.init(data: nil, ofType: nil)
    }

    /// Required Initializer
    ///
    required public init?(coder aDecoder: NSCoder) {
        identifier = ""
        super.init(coder: aDecoder)

        if let decodedIndentifier = aDecoder.decodeObject(forKey: EncodeKeys.identifier.rawValue) as? String {
            identifier = decodedIndentifier
        }
        if aDecoder.containsValue(forKey: EncodeKeys.url.rawValue) {
            url = aDecoder.decodeObject(forKey: EncodeKeys.url.rawValue) as? URL
        }
        if aDecoder.containsValue(forKey: EncodeKeys.alignment.rawValue) {
            let alignmentRaw = aDecoder.decodeInteger(forKey: EncodeKeys.alignment.rawValue)
            if let alignment = Alignment(rawValue:alignmentRaw) {
                self.alignment = alignment
            }
        }
        if aDecoder.containsValue(forKey: EncodeKeys.size.rawValue) {
            let sizeRaw = aDecoder.decodeInteger(forKey: EncodeKeys.size.rawValue)
            if let size = Size(rawValue:sizeRaw) {
                self.size = size
            }
        }
    }

    override init(data contentData: Data?, ofType uti: String?) {
        identifier = ""
        url = nil
        super.init(data: contentData, ofType: uti)
    }

    fileprivate func setupDefaultAppearance() {
        progressHeight = TextAttachment.appearance.progressHeight
        progressBackgroundColor = TextAttachment.appearance.progressBackgroundColor
        progressColor = TextAttachment.appearance.progressColor
        overlayColor = TextAttachment.appearance.overlayColor
    }

    override open func encode(with aCoder: NSCoder) {
        super.encode(with: aCoder)
        aCoder.encode(identifier, forKey: EncodeKeys.identifier.rawValue)
        if let url = self.url {
            aCoder.encode(url, forKey: EncodeKeys.url.rawValue)
        }
        aCoder.encode(alignment.rawValue, forKey: EncodeKeys.alignment.rawValue)
        aCoder.encode(size.rawValue, forKey: EncodeKeys.size.rawValue)
    }

    fileprivate enum EncodeKeys: String {
        case identifier
        case url
        case alignment
        case size
    }
    // MARK: - Origin calculation

    fileprivate func xPosition(forContainerWidth containerWidth: CGFloat) -> CGFloat {
        let imageWidth = onScreenWidth(containerWidth)

        switch (alignment) {
        case .center:
            return CGFloat(floor((containerWidth - imageWidth) / 2))
        case .right:
            return CGFloat(floor(containerWidth - imageWidth))
        default:
            return 0
        }
    }

    fileprivate func onScreenHeight(_ containerWidth: CGFloat) -> CGFloat {
        if let image = image {
            let targetWidth = onScreenWidth(containerWidth)
            let scale = targetWidth / image.size.width

            return floor(image.size.height * scale) + (imageMargin * 2)
        } else {
            return 0
        }
    }

    fileprivate func onScreenWidth(_ containerWidth: CGFloat) -> CGFloat {
        if let image = image {
            switch (size) {	
            case .full:
                return floor(min(image.size.width, containerWidth))
            default:
                return floor(min(min(image.size.width,size.width), containerWidth))
            }
        } else {
            return 0
        }
    }

    // MARK: - NSTextAttachmentContainer

    override open func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? {
        
        updateImage(inTextContainer: textContainer)

        guard let image = image else {
            return nil
        }

        if let cachedImage = glyphImage, imageBounds.size.equalTo(cachedImage.size) {
            return cachedImage
        }

        glyphImage = glyph(basedOnImage:image, forBounds: imageBounds)

        return glyphImage
    }

    fileprivate func glyph(basedOnImage image:UIImage, forBounds bounds: CGRect) -> UIImage? {

        let containerWidth = bounds.size.width
        let origin = CGPoint(x: xPosition(forContainerWidth: bounds.size.width), y: imageMargin)
        let size = CGSize(width: onScreenWidth(containerWidth), height: onScreenHeight(containerWidth))

        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)

        image.draw(in: CGRect(origin: origin, size: size))

        if message != nil || progress != nil {
            let box = UIBezierPath()
            box.move(to: CGPoint(x:origin.x, y:origin.y))
            box.addLine(to: CGPoint(x: origin.x + size.width, y: origin.y))
            box.addLine(to: CGPoint(x: origin.x + size.width, y: origin.y + size.height))
            box.addLine(to: CGPoint(x: origin.x, y: origin.y + size.height))
            box.addLine(to: CGPoint(x: origin.x, y: origin.y))
            box.lineWidth = 2.0
            overlayColor.setFill()<commit_msg>Add some margins around the image.<commit_after>import Foundation
import UIKit

protocol TextAttachmentImageProvider {
    func textAttachment(
        _ textAttachment: TextAttachment,
        imageForURL url: URL,
        onSuccess success: @escaping (UIImage) -> (),
        onFailure failure: @escaping () -> ()) -> UIImage
}

/// Custom text attachment.
///
open class TextAttachment: NSTextAttachment
{
    public struct Appearance {
        public var overlayColor = UIColor(white: 0.6, alpha: 0.6)

        /// The height of the progress bar for progress indicators
        public var progressHeight = CGFloat(2.0)

        /// The color to use when drawing the backkground of the progress indicators
        ///
        public var progressBackgroundColor = UIColor.cyan

        /// The color to use when drawing progress indicators
        ///
        public var progressColor = UIColor.blue

        /// The margin apply to the images being displayed. This is to avoid that two images in a row get glued together.
        ///
        public var imageMargin = CGFloat(10.0)
    }


    /// This property allows the global customization of appearance properties of the TextAttachment
    ///
    open static var appearance: Appearance = Appearance()

    /// Identifier used to match this attachment with a custom UIView subclass
    ///
    fileprivate(set) open var identifier: String
    
    /// Attachment URL
    ///
    open var url: URL?
    fileprivate var lastRequestedURL: URL?

    /// Attachment Alignment
    ///
    open var alignment: Alignment = .center {
        willSet {
            if newValue != alignment {
                glyphImage = nil
            }
        }
    }

    /// Attachment Size
    ///
    open var size: Size = .full {
        willSet {
            if newValue != size {
                glyphImage = nil
            }
        }
    }

    /// A progress value that indicates the progress of an attachment. It can be set between values 0 and 1
    ///
    open var progress: Double? = nil {
        willSet {
            assert(newValue == nil || (newValue! >= 0 && newValue! <= 1), "Progress must be value between 0 and 1 or nil")
            if newValue != progress {
                glyphImage = nil
            }
        }
    }

    /// The color to use when drawing the background overlay for messages, icons, and progress
    ///
    open var overlayColor: UIColor = TextAttachment.appearance.overlayColor

    /// The height of the progress bar for progress indicators
    open var progressHeight: CGFloat = TextAttachment.appearance.progressHeight

    /// The color to use when drawing the backkground of the progress indicators
    ///
    open var progressBackgroundColor: UIColor = TextAttachment.appearance.progressBackgroundColor

    /// The color to use when drawing progress indicators
    ///
    open var progressColor: UIColor = TextAttachment.appearance.progressColor

    /// The margin apply to the images being displayed. This is to avoid that two images in a row get glued together.
    ///
    open var imageMargin: CGFloat = TextAttachment.appearance.imageMargin

    /// A message to display overlaid on top of the image
    ///
    open var message: NSAttributedString? {
        willSet {
            if newValue != message {
                glyphImage = nil
            }
        }
    }

    /// An image to display overlaid on top of the image has an action icon.
    ///
    open var overlayImage: UIImage? {
        willSet {
            if newValue != overlayImage {
                glyphImage = nil
            }
        }
    }


    /// Clears all overlay information that is applied to the attachment
    ///
    open func clearAllOverlays() {
        progress = nil
        message = nil
        overlayImage = nil
    }

    fileprivate var glyphImage: UIImage?

    var imageProvider: TextAttachmentImageProvider?
    
    var isFetchingImage: Bool = false

    /// Creates a new attachment
    ///
    /// - parameter identifier: An unique identifier for the attachment
    ///
    /// - returns: self, initilized with the identifier a with kind = .MissingImage
    ///
    required public init(identifier: String, url: URL? = nil) {
        self.identifier = identifier
        self.url = url
        
        super.init(data: nil, ofType: nil)
    }

    /// Required Initializer
    ///
    required public init?(coder aDecoder: NSCoder) {
        identifier = ""
        super.init(coder: aDecoder)

        if let decodedIndentifier = aDecoder.decodeObject(forKey: EncodeKeys.identifier.rawValue) as? String {
            identifier = decodedIndentifier
        }
        if aDecoder.containsValue(forKey: EncodeKeys.url.rawValue) {
            url = aDecoder.decodeObject(forKey: EncodeKeys.url.rawValue) as? URL
        }
        if aDecoder.containsValue(forKey: EncodeKeys.alignment.rawValue) {
            let alignmentRaw = aDecoder.decodeInteger(forKey: EncodeKeys.alignment.rawValue)
            if let alignment = Alignment(rawValue:alignmentRaw) {
                self.alignment = alignment
            }
        }
        if aDecoder.containsValue(forKey: EncodeKeys.size.rawValue) {
            let sizeRaw = aDecoder.decodeInteger(forKey: EncodeKeys.size.rawValue)
            if let size = Size(rawValue:sizeRaw) {
                self.size = size
            }
        }
    }

    override init(data contentData: Data?, ofType uti: String?) {
        identifier = ""
        url = nil
        super.init(data: contentData, ofType: uti)
    }

    fileprivate func setupDefaultAppearance() {
        progressHeight = TextAttachment.appearance.progressHeight
        progressBackgroundColor = TextAttachment.appearance.progressBackgroundColor
        progressColor = TextAttachment.appearance.progressColor
        overlayColor = TextAttachment.appearance.overlayColor
    }

    override open func encode(with aCoder: NSCoder) {
        super.encode(with: aCoder)
        aCoder.encode(identifier, forKey: EncodeKeys.identifier.rawValue)
        if let url = self.url {
            aCoder.encode(url, forKey: EncodeKeys.url.rawValue)
        }
        aCoder.encode(alignment.rawValue, forKey: EncodeKeys.alignment.rawValue)
        aCoder.encode(size.rawValue, forKey: EncodeKeys.size.rawValue)
    }

    fileprivate enum EncodeKeys: String {
        case identifier
        case url
        case alignment
        case size
    }
    // MARK: - Origin calculation

    fileprivate func xPosition(forContainerWidth containerWidth: CGFloat) -> CGFloat {
        let imageWidth = onScreenWidth(containerWidth)

        switch (alignment) {
        case .center:
            return CGFloat(floor((containerWidth - imageWidth) / 2))
        case .right:
            return CGFloat(floor(containerWidth - imageWidth))
        default:
            return 0
        }
    }

    fileprivate func onScreenHeight(_ containerWidth: CGFloat) -> CGFloat {
        if let image = image {
            let targetWidth = onScreenWidth(containerWidth)
            let scale = targetWidth / image.size.width

            return floor(image.size.height * scale) + (imageMargin * 2)
        } else {
            return 0
        }
    }

    fileprivate func onScreenWidth(_ containerWidth: CGFloat) -> CGFloat {
        if let image = image {
            switch (size) {	
            case .full:
                return floor(min(image.size.width, containerWidth))
            default:
                return floor(min(min(image.size.width,size.width), containerWidth))
            }
        } else {
            return 0
        }
    }

    // MARK: - NSTextAttachmentContainer

    override open func image(forBounds imageBounds: CGRect, textContainer: NSTextContainer?, characterIndex charIndex: Int) -> UIImage? {
        
        updateImage(inTextContainer: textContainer)

        guard let image = image else {
            return nil
        }

        if let cachedImage = glyphImage, imageBounds.size.equalTo(cachedImage.size) {
            return cachedImage
        }

        glyphImage = glyph(basedOnImage:image, forBounds: imageBounds)

        return glyphImage
    }

    fileprivate func glyph(basedOnImage image:UIImage, forBounds bounds: CGRect) -> UIImage? {

        let containerWidth = bounds.size.width
        let origin = CGPoint(x: xPosition(forContainerWidth: bounds.size.width), y: imageMargin)
        let size = CGSize(width: onScreenWidth(containerWidth), height: onScreenHeight(containerWidth) - imageMargin)

        UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0)

        image.draw(in: CGRect(origin: origin, size: size))

        if message != nil || progress != nil {
            let box = UIBezierPath()
            box.move(to: CGPoint(x:origin.x, y:origin.y))
            box.addLine(to: CGPoint(x: origin.x + size.width, y: origin.y))
            box.addLine(to: CGPoint(x: origin.x + size.width, y: origin.y + size.height))
            box.addLine(to: CGPoint(x: origin.x, y: origin.y + size.height))
            box.addLine(to: CGPoint(x: origin.x, y: origin.y))
            box.lineWidth = 2.0
            overlayColor.setFill()
{'fraction_non_alphanumeric': 0.08484982576469938, 'fraction_numerical': 0.002544388517063997, 'mean_word_length': 2.095890410958904, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 21, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6707923', 'n_tokens_mistral': 4929, 'n_tokens_neox': 4738, 'n_words': 1397}