Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,289 @@
import Foundation
import TelegramCore
import UrlEscaping
public func decodeCodableDrawingEntities(data: Data) -> [CodableDrawingEntity] {
if let codableEntities = try? JSONDecoder().decode([CodableDrawingEntity].self, from: data) {
return codableEntities
}
return []
}
public func decodeDrawingEntities(data: Data) -> [DrawingEntity] {
return decodeCodableDrawingEntities(data: data).map { $0.entity }
}
public enum CodableDrawingEntity: Equatable {
public static func == (lhs: CodableDrawingEntity, rhs: CodableDrawingEntity) -> Bool {
return lhs.entity.isEqual(to: rhs.entity)
}
case sticker(DrawingStickerEntity)
case text(DrawingTextEntity)
case simpleShape(DrawingSimpleShapeEntity)
case bubble(DrawingBubbleEntity)
case vector(DrawingVectorEntity)
case location(DrawingLocationEntity)
case link(DrawingLinkEntity)
case weather(DrawingWeatherEntity)
public init?(entity: DrawingEntity) {
if let entity = entity as? DrawingStickerEntity {
self = .sticker(entity)
} else if let entity = entity as? DrawingTextEntity {
self = .text(entity)
} else if let entity = entity as? DrawingSimpleShapeEntity {
self = .simpleShape(entity)
} else if let entity = entity as? DrawingBubbleEntity {
self = .bubble(entity)
} else if let entity = entity as? DrawingVectorEntity {
self = .vector(entity)
} else if let entity = entity as? DrawingLocationEntity {
self = .location(entity)
} else if let entity = entity as? DrawingLinkEntity {
self = .link(entity)
} else if let entity = entity as? DrawingWeatherEntity {
self = .weather(entity)
} else {
return nil
}
}
public var entity: DrawingEntity {
switch self {
case let .sticker(entity):
return entity
case let .text(entity):
return entity
case let .simpleShape(entity):
return entity
case let .bubble(entity):
return entity
case let .vector(entity):
return entity
case let .location(entity):
return entity
case let .link(entity):
return entity
case let .weather(entity):
return entity
}
}
private var coordinates: MediaArea.Coordinates? {
var position: CGPoint?
var size: CGSize?
var rotation: CGFloat?
var scale: CGFloat?
var cornerRadius: Double?
switch self {
case let .location(entity):
position = entity.position
size = entity.renderImage?.size
rotation = entity.rotation
scale = entity.scale
if let size {
cornerRadius = 10.0 / (size.width * entity.scale)
}
case let .sticker(entity):
var entityPosition = entity.position
var entitySize = entity.baseSize
let entityRotation = entity.rotation
let entityScale = entity.scale
if case .message = entity.content {
let offset: CGFloat = 16.18 * entityScale
entitySize = CGSize(width: entitySize.width - 38.0, height: entitySize.height - 4.0)
entityPosition = CGPoint(x: entityPosition.x + offset * cos(entityRotation), y: entityPosition.y + offset * sin(entityRotation))
}
position = entityPosition
size = entitySize
rotation = entityRotation
scale = entityScale
case let .link(entity):
position = entity.position
rotation = entity.rotation
scale = entity.scale
if let entitySize = entity.renderImage?.size {
if entity.whiteImage != nil {
cornerRadius = 38.0 / (entitySize.width * entity.scale)
size = CGSize(width: entitySize.width - 28.0, height: entitySize.height - 26.0)
} else {
cornerRadius = 10.0 / (entitySize.width * entity.scale)
size = entitySize
}
}
case let .weather(entity):
position = entity.position
size = entity.renderImage?.size
rotation = entity.rotation
scale = entity.scale
if let size {
cornerRadius = (size.height * 0.17) / size.width
}
default:
return nil
}
guard let position, let size, let scale, let rotation else {
return nil
}
let width = size.width * scale / 1080.0 * 100.0
let height = size.height * scale / 1920.0 * 100.0
return MediaArea.Coordinates(
x: position.x / 1080.0 * 100.0,
y: position.y / 1920.0 * 100.0,
width: width,
height: height,
rotation: rotation / .pi * 180.0,
cornerRadius: cornerRadius.flatMap { $0 * 100.0 }
)
}
public var mediaArea: MediaArea? {
guard let coordinates = self.coordinates else {
return nil
}
switch self {
case let .location(entity):
return .venue(
coordinates: coordinates,
venue: MediaArea.Venue(
latitude: entity.location.latitude,
longitude: entity.location.longitude,
venue: entity.location.venue,
address: entity.location.address,
queryId: entity.queryId,
resultId: entity.resultId
)
)
case let .sticker(entity):
if case let .file(_, type) = entity.content, case let .reaction(reaction, style) = type {
var flags: MediaArea.ReactionFlags = []
if case .black = style {
flags.insert(.isDark)
}
if entity.mirrored {
flags.insert(.isFlipped)
}
return .reaction(
coordinates: coordinates,
reaction: reaction,
flags: flags
)
} else if case let .message(messageIds, _, _, _, _) = entity.content, let messageId = messageIds.first {
return .channelMessage(
coordinates: coordinates,
messageId: messageId
)
} else if case let .gift(gift, _) = entity.content {
return .starGift(
coordinates: coordinates,
slug: gift.slug
)
} else {
return nil
}
case let .link(entity):
return .link(
coordinates: coordinates,
url: explicitUrl(entity.url)
)
case let .weather(entity):
let color: UInt32
switch entity.style {
case .white:
color = 0xffffffff
case .black:
color = 0xff000000
case .transparent:
color = 0x51000000
case .custom:
color = entity.color.toUIColor().argb
}
return .weather(
coordinates: coordinates,
emoji: entity.emoji,
temperature: entity.temperature,
color: Int32(bitPattern: color)
)
default:
return nil
}
}
}
extension CodableDrawingEntity: Codable {
private enum CodingKeys: String, CodingKey {
case type
case entity
}
private enum EntityType: Int, Codable {
case sticker
case text
case simpleShape
case bubble
case vector
case location
case link
case weather
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(EntityType.self, forKey: .type)
switch type {
case .sticker:
self = .sticker(try container.decode(DrawingStickerEntity.self, forKey: .entity))
case .text:
self = .text(try container.decode(DrawingTextEntity.self, forKey: .entity))
case .simpleShape:
self = .simpleShape(try container.decode(DrawingSimpleShapeEntity.self, forKey: .entity))
case .bubble:
self = .bubble(try container.decode(DrawingBubbleEntity.self, forKey: .entity))
case .vector:
self = .vector(try container.decode(DrawingVectorEntity.self, forKey: .entity))
case .location:
self = .location(try container.decode(DrawingLocationEntity.self, forKey: .entity))
case .link:
self = .link(try container.decode(DrawingLinkEntity.self, forKey: .entity))
case .weather:
self = .weather(try container.decode(DrawingWeatherEntity.self, forKey: .entity))
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
switch self {
case let .sticker(payload):
try container.encode(EntityType.sticker, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .text(payload):
try container.encode(EntityType.text, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .simpleShape(payload):
try container.encode(EntityType.simpleShape, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .bubble(payload):
try container.encode(EntityType.bubble, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .vector(payload):
try container.encode(EntityType.vector, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .location(payload):
try container.encode(EntityType.location, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .link(payload):
try container.encode(EntityType.link, forKey: .type)
try container.encode(payload, forKey: .entity)
case let .weather(payload):
try container.encode(EntityType.weather, forKey: .type)
try container.encode(payload, forKey: .entity)
}
}
}
@@ -0,0 +1,144 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingBubbleEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case tailPosition
case renderImage
}
public enum DrawType: Codable {
case fill
case stroke
}
public var uuid: UUID
public let isAnimated: Bool
public var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
public var tailPosition: CGPoint
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
self.tailPosition = CGPoint(x: 0.16, y: 0.18)
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.tailPosition = try container.decode(CGPoint.self, forKey: .tailPosition)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.tailPosition, forKey: .tailPosition)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingBubbleEntity(drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingBubbleEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.drawType != other.drawType {
return false
}
if self.color != other.color {
return false
}
if self.lineWidth != other.lineWidth {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.size != other.size {
return false
}
if self.rotation != other.rotation {
return false
}
if self.tailPosition != other.tailPosition {
return false
}
return true
}
}
@@ -0,0 +1,161 @@
import Foundation
import UIKit
import simd
public struct DrawingColor: Equatable, Codable {
private enum CodingKeys: String, CodingKey {
case red
case green
case blue
case alpha
case position
}
public static var clear = DrawingColor(red: 0.0, green: 0.0, blue: 0.0, alpha: 0.0)
public var red: CGFloat
public var green: CGFloat
public var blue: CGFloat
public var alpha: CGFloat
public var position: CGPoint?
public var isClear: Bool {
return self.red.isZero && self.green.isZero && self.blue.isZero && self.alpha.isZero
}
public init(
red: CGFloat,
green: CGFloat,
blue: CGFloat,
alpha: CGFloat = 1.0,
position: CGPoint? = nil
) {
self.red = red
self.green = green
self.blue = blue
self.alpha = alpha
self.position = position
}
public init(color: UIColor) {
var red: CGFloat = 0.0
var green: CGFloat = 0.0
var blue: CGFloat = 0.0
var alpha: CGFloat = 1.0
if color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) {
self.init(red: red, green: green, blue: blue, alpha: alpha)
} else if color.getWhite(&red, alpha: &alpha) {
self.init(red: red, green: red, blue: red, alpha: alpha)
} else {
self.init(red: 0.0, green: 0.0, blue: 0.0)
}
}
public init(rgb: UInt32) {
self.init(color: UIColor(rgb: rgb))
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.red = try container.decode(CGFloat.self, forKey: .red)
self.green = try container.decode(CGFloat.self, forKey: .green)
self.blue = try container.decode(CGFloat.self, forKey: .blue)
self.alpha = try container.decode(CGFloat.self, forKey: .alpha)
self.position = try container.decodeIfPresent(CGPoint.self, forKey: .position)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.red, forKey: .red)
try container.encode(self.green, forKey: .green)
try container.encode(self.blue, forKey: .blue)
try container.encode(self.alpha, forKey: .alpha)
try container.encodeIfPresent(self.position, forKey: .position)
}
public func withUpdatedRed(_ red: CGFloat) -> DrawingColor {
return DrawingColor(
red: red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
public func withUpdatedGreen(_ green: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: green,
blue: self.blue,
alpha: self.alpha
)
}
public func withUpdatedBlue(_ blue: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: blue,
alpha: self.alpha
)
}
public func withUpdatedAlpha(_ alpha: CGFloat) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: alpha,
position: self.position
)
}
public func withUpdatedPosition(_ position: CGPoint) -> DrawingColor {
return DrawingColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha,
position: position
)
}
public func toUIColor() -> UIColor {
return UIColor(
red: self.red,
green: self.green,
blue: self.blue,
alpha: self.alpha
)
}
public func toCGColor() -> CGColor {
return self.toUIColor().cgColor
}
public func toFloat4() -> vector_float4 {
return [
simd_float1(self.red),
simd_float1(self.green),
simd_float1(self.blue),
simd_float1(self.alpha)
]
}
public static func ==(lhs: DrawingColor, rhs: DrawingColor) -> Bool {
if lhs.red != rhs.red {
return false
}
if lhs.green != rhs.green {
return false
}
if lhs.blue != rhs.blue {
return false
}
if lhs.alpha != rhs.alpha {
return false
}
return true
}
}
@@ -0,0 +1,22 @@
import Foundation
import UIKit
public protocol DrawingEntity: AnyObject {
var uuid: UUID { get set }
var isAnimated: Bool { get }
var center: CGPoint { get }
var isMedia: Bool { get }
var lineWidth: CGFloat { get set }
var color: DrawingColor { get set }
var scale: CGFloat { get set }
func duplicate(copy: Bool) -> DrawingEntity
var renderImage: UIImage? { get set }
var renderSubEntities: [DrawingEntity]? { get set }
func isEqual(to other: DrawingEntity) -> Bool
}
@@ -0,0 +1,260 @@
import Foundation
import UIKit
import Display
import AccountContext
import TextFormat
import Postbox
import TelegramCore
public final class DrawingLinkEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case url
case name
case webpage
case positionBelowText
case largeMedia
case expandedSize
case style
case color
case hasCustomColor
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
case whiteImage
case blackImage
}
public enum Style: Codable, Equatable {
case white
case black
case transparent
case custom
case blur
}
public var uuid: UUID
public var isAnimated: Bool {
return false
}
public var url: String
public var name: String
public var webpage: TelegramMediaWebpage?
public var positionBelowText: Bool
public var largeMedia: Bool?
public var expandedSize: CGSize?
public var style: Style
public var color: DrawingColor = DrawingColor(color: .white) {
didSet {
if self.color.toUIColor().argb == UIColor.white.argb {
self.style = .white
self.hasCustomColor = false
} else {
self.style = .custom
self.hasCustomColor = true
}
}
}
public var hasCustomColor = false
public var lineWidth: CGFloat = 0.0
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var width: CGFloat
public var scale: CGFloat {
didSet {
self.scale = min(2.5, self.scale)
}
}
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var whiteImage: UIImage?
public var blackImage: UIImage?
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(
url: String,
name: String,
webpage: TelegramMediaWebpage?,
positionBelowText: Bool,
largeMedia: Bool?,
style: Style
) {
self.uuid = UUID()
self.url = url
self.name = name
self.webpage = webpage
self.positionBelowText = positionBelowText
self.largeMedia = largeMedia
self.style = style
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.url = try container.decode(String.self, forKey: .url)
self.name = try container.decode(String.self, forKey: .name)
self.positionBelowText = try container.decode(Bool.self, forKey: .positionBelowText)
self.largeMedia = try container.decodeIfPresent(Bool.self, forKey: .largeMedia)
self.style = try container.decode(Style.self, forKey: .style)
if let webpageData = try container.decodeIfPresent(Data.self, forKey: .webpage) {
self.webpage = PostboxDecoder(buffer: MemoryBuffer(data: webpageData)).decodeRootObject() as? TelegramMediaWebpage
} else {
self.webpage = nil
}
self.color = try container.decodeIfPresent(DrawingColor.self, forKey: .color) ?? DrawingColor(color: .white)
self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let imagePath = try container.decodeIfPresent(String.self, forKey: .whiteImage), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.whiteImage = image
}
if let imagePath = try container.decodeIfPresent(String.self, forKey: .blackImage), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.blackImage = image
}
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.url, forKey: .url)
try container.encode(self.name, forKey: .name)
try container.encode(self.positionBelowText, forKey: .positionBelowText)
try container.encodeIfPresent(self.largeMedia, forKey: .largeMedia)
if let webpage = self.webpage {
let encoder = PostboxEncoder()
encoder.encodeRootObject(webpage)
let webpageData = encoder.makeData()
try container.encode(webpageData, forKey: .webpage)
} else {
try container.encodeNil(forKey: .webpage)
}
try container.encode(self.style, forKey: .style)
try container.encode(self.color, forKey: .color)
try container.encode(self.hasCustomColor, forKey: .hasCustomColor)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let image = self.whiteImage {
let imagePath = "\(self.uuid)_white.png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .whiteImage)
}
}
if let image = self.blackImage {
let imagePath = "\(self.uuid)black.png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .blackImage)
}
}
if let renderImage = self.renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingLinkEntity(url: self.url, name: self.name, webpage: self.webpage, positionBelowText: self.positionBelowText, largeMedia: self.largeMedia, style: self.style)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.whiteImage = self.whiteImage
newEntity.blackImage = self.blackImage
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingLinkEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.url != other.url {
return false
}
if self.name != other.name {
return false
}
if self.webpage != other.webpage {
return false
}
if self.positionBelowText != other.positionBelowText {
return false
}
if self.largeMedia != other.largeMedia {
return false
}
if self.style != other.style {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.width != other.width {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
return true
}
}
@@ -0,0 +1,216 @@
import Foundation
import UIKit
import Display
import AccountContext
import TextFormat
import Postbox
import TelegramCore
public final class DrawingLocationEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case title
case style
case color
case hasCustomColor
case location
case icon
case queryId
case resultId
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
}
public enum Style: Codable, Equatable {
case white
case black
case transparent
case custom
case blur
}
public var uuid: UUID
public var isAnimated: Bool {
return false
}
public var title: String
public var style: Style
public var location: TelegramMediaMap
public var icon: TelegramMediaFile?
public var queryId: Int64?
public var resultId: String?
public var color: DrawingColor = DrawingColor(color: .white) {
didSet {
if self.color.toUIColor().argb == UIColor.white.argb {
self.style = .white
self.hasCustomColor = false
} else {
self.style = .custom
self.hasCustomColor = true
}
}
}
public var hasCustomColor = false
public var lineWidth: CGFloat = 0.0
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var width: CGFloat
public var scale: CGFloat {
didSet {
self.scale = min(2.5, self.scale)
}
}
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(title: String, style: Style, location: TelegramMediaMap, icon: TelegramMediaFile?, queryId: Int64?, resultId: String?) {
self.uuid = UUID()
self.title = title
self.style = style
self.location = location
self.icon = icon
self.queryId = queryId
self.resultId = resultId
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.title = try container.decode(String.self, forKey: .title)
self.style = try container.decode(Style.self, forKey: .style)
self.color = try container.decodeIfPresent(DrawingColor.self, forKey: .color) ?? DrawingColor(color: .white)
self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false
if let locationData = try container.decodeIfPresent(Data.self, forKey: .location) {
self.location = PostboxDecoder(buffer: MemoryBuffer(data: locationData)).decodeRootObject() as! TelegramMediaMap
} else {
fatalError()
}
if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) {
self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile
}
self.queryId = try container.decodeIfPresent(Int64.self, forKey: .queryId)
self.resultId = try container.decodeIfPresent(String.self, forKey: .resultId)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.title, forKey: .title)
try container.encode(self.style, forKey: .style)
try container.encode(self.color, forKey: .color)
try container.encode(self.hasCustomColor, forKey: .hasCustomColor)
var encoder = PostboxEncoder()
encoder.encodeRootObject(self.location)
let locationData = encoder.makeData()
try container.encode(locationData, forKey: .location)
if let icon = self.icon {
encoder = PostboxEncoder()
encoder.encodeRootObject(icon)
let iconData = encoder.makeData()
try container.encode(iconData, forKey: .icon)
}
try container.encodeIfPresent(self.queryId, forKey: .queryId)
try container.encodeIfPresent(self.resultId, forKey: .resultId)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingLocationEntity(title: self.title, style: self.style, location: self.location, icon: self.icon, queryId: self.queryId, resultId: self.resultId)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingLocationEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.title != other.title {
return false
}
if self.style != other.style {
return false
}
if self.location != other.location {
return false
}
if self.queryId != other.queryId {
return false
}
if self.resultId != other.resultId {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.width != other.width {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
return true
}
}
@@ -0,0 +1,121 @@
import Foundation
import UIKit
import Display
import SwiftSignalKit
import TelegramCore
import AccountContext
import Photos
public final class DrawingMediaEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case size
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
}
public var uuid: UUID
public let size: CGSize
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat
public var rotation: CGFloat
public var mirrored: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
return self.size
}
public var isAnimated: Bool {
return false
}
public var isMedia: Bool {
return true
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public init(size: CGSize) {
self.uuid = UUID()
self.size = size
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.size = try container.decode(CGSize.self, forKey: .size)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.size, forKey: .size)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingMediaEntity(size: self.size)
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingMediaEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.size != other.size {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
if self.mirrored != other.mirrored {
return false
}
return true
}
}
@@ -0,0 +1,150 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingSimpleShapeEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case shapeType
case drawType
case color
case lineWidth
case referenceDrawingSize
case position
case size
case rotation
case renderImage
}
public enum ShapeType: Codable {
case rectangle
case ellipse
case star
}
public enum DrawType: Codable {
case fill
case stroke
}
public var uuid: UUID
public let isAnimated: Bool
public var shapeType: ShapeType
public var drawType: DrawType
public var color: DrawingColor
public var lineWidth: CGFloat
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var size: CGSize
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(shapeType: ShapeType, drawType: DrawType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.shapeType = shapeType
self.drawType = drawType
self.color = color
self.lineWidth = lineWidth
self.referenceDrawingSize = .zero
self.position = .zero
self.size = CGSize(width: 1.0, height: 1.0)
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.shapeType = try container.decode(ShapeType.self, forKey: .shapeType)
self.drawType = try container.decode(DrawType.self, forKey: .drawType)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.size = try container.decode(CGSize.self, forKey: .size)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.shapeType, forKey: .shapeType)
try container.encode(self.drawType, forKey: .drawType)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.size, forKey: .size)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingSimpleShapeEntity(shapeType: self.shapeType, drawType: self.drawType, color: self.color, lineWidth: self.lineWidth)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.size = self.size
newEntity.rotation = self.rotation
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingSimpleShapeEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.shapeType != other.shapeType {
return false
}
if self.drawType != other.drawType {
return false
}
if self.color != other.color {
return false
}
if self.lineWidth != other.lineWidth {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.size != other.size {
return false
}
if self.rotation != other.rotation {
return false
}
return true
}
}
@@ -0,0 +1,491 @@
import Foundation
import UIKit
import Display
import AccountContext
import Postbox
import TelegramCore
func entitiesPath() -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/mediaEntities"
}
func fullEntityMediaPath(_ path: String) -> String {
return NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0] + "/mediaEntities/" + path
}
public final class DrawingStickerEntity: DrawingEntity, Codable {
public enum DecodingError: Error {
case generic
}
public enum Content: Equatable {
public enum ImageType: Equatable {
case sticker
case rectangle
case dualPhoto
}
public enum FileType: Equatable {
public enum ReactionStyle: Int32 {
case white
case black
}
case sticker
case reaction(MessageReaction.Reaction, ReactionStyle)
}
case file(FileMediaReference, FileType)
case image(UIImage, ImageType)
case animatedImage(Data, UIImage)
case video(TelegramMediaFile)
case dualVideoReference(Bool)
case message([MessageId], CGSize, TelegramMediaFile?, CGRect?, CGFloat?)
case gift(StarGift.UniqueGift, CGSize)
public static func == (lhs: Content, rhs: Content) -> Bool {
switch lhs {
case let .file(lhsFile, lhsFileType):
if case let .file(rhsFile, rhsFileType) = rhs {
return lhsFile.media.fileId == rhsFile.media.fileId && lhsFileType == rhsFileType
} else {
return false
}
case let .image(lhsImage, lhsImageType):
if case let .image(rhsImage, rhsImageType) = rhs {
return lhsImage === rhsImage && lhsImageType == rhsImageType
} else {
return false
}
case let .animatedImage(lhsData, lhsThumbnailImage):
if case let .animatedImage(rhsData, rhsThumbnailImage) = lhs {
return lhsData == rhsData && lhsThumbnailImage === rhsThumbnailImage
} else {
return false
}
case let .video(lhsFile):
if case let .video(rhsFile) = rhs {
return lhsFile.fileId == rhsFile.fileId
} else {
return false
}
case let .dualVideoReference(isAdditional):
if case .dualVideoReference(isAdditional) = rhs {
return true
} else {
return false
}
case let .message(lhsMessageIds, lhsSize, lhsFile, lhsMediaFrame, lhsCornerRadius):
if case let .message(rhsMessageIds, rhsSize, rhsFile, rhsMediaFrame, rhsCornerRadius) = rhs {
return lhsMessageIds == rhsMessageIds && lhsSize == rhsSize && lhsFile?.fileId == rhsFile?.fileId && lhsMediaFrame == rhsMediaFrame && lhsCornerRadius == rhsCornerRadius
} else {
return false
}
case let .gift(lhsGift, lhsSize):
if case let .gift(rhsGift, rhsSize) = rhs {
return lhsGift == rhsGift && lhsSize == rhsSize
} else {
return false
}
}
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case file
case reaction
case reactionStyle
case imagePath
case animatedImagePath
case videoFile
case isRectangle
case isDualPhoto
case dualVideo
case isAdditionalVideo
case messageIds
case messageFile
case messageSize
case messageMediaRect
case messageMediaCornerRadius
case gift
case referenceDrawingSize
case position
case scale
case rotation
case mirrored
case isExplicitlyStatic
case canCutOut
case renderImage
case renderSubEntities
}
public var uuid: UUID
public var content: Content
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var scale: CGFloat {
didSet {
if case let .file(_, type) = self.content, case .reaction = type {
self.scale = max(0.59, min(1.77, self.scale))
} else if case .message = self.content {
self.scale = max(2.5, self.scale)
} else if case .gift = self.content {
self.scale = max(2.5, self.scale)
}
}
}
public var rotation: CGFloat
public var mirrored: Bool
public var canCutOut = false
public var isExplicitlyStatic: Bool
public var color: DrawingColor = DrawingColor.clear
public var lineWidth: CGFloat = 0.0
public var secondaryRenderImage: UIImage?
public var overlayRenderImage: UIImage?
public var tertiaryRenderImage: UIImage?
public var quaternaryRenderImage: UIImage?
public var center: CGPoint {
return self.position
}
public var baseSize: CGSize {
let size = max(10.0, min(self.referenceDrawingSize.width, self.referenceDrawingSize.height) * 0.25)
let dimensions: CGSize
switch self.content {
case let .image(image, _):
dimensions = image.size
case let .animatedImage(_, thumbnailImage):
dimensions = thumbnailImage.size
case let .file(file, type):
if case .reaction = type {
dimensions = CGSize(width: 512.0, height: 512.0)
} else {
dimensions = file.media.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
}
case let .video(file):
dimensions = file.dimensions?.cgSize ?? CGSize(width: 512.0, height: 512.0)
case .dualVideoReference:
dimensions = CGSize(width: 512.0, height: 512.0)
case let .message(_, size, _, _, _):
dimensions = size
case let .gift(_, size):
dimensions = size
}
let boundingSize = CGSize(width: size, height: size)
return dimensions.fitted(boundingSize)
}
public var isAnimated: Bool {
switch self.content {
case let .file(file, type):
if self.isExplicitlyStatic {
return false
} else {
switch type {
case .reaction:
return false
default:
return file.media.isAnimatedSticker || file.media.isVideoSticker || file.media.mimeType == "video/webm"
}
}
case .image:
return false
case .animatedImage:
return true
case .video:
return true
case .dualVideoReference:
return true
case .message, .gift:
return !(self.renderSubEntities ?? []).isEmpty
}
}
public var isRectangle: Bool {
switch self.content {
case let .image(_, imageType):
return imageType == .rectangle
case .video:
return true
case .message, .gift:
return true
default:
return false
}
}
public var isMedia: Bool {
return false
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public init(content: Content) {
self.uuid = UUID()
self.content = content
self.referenceDrawingSize = .zero
self.position = CGPoint()
self.scale = 1.0
self.rotation = 0.0
self.mirrored = false
self.isExplicitlyStatic = false
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
if let gift = try container.decodeIfPresent(StarGift.UniqueGift.self, forKey: .gift) {
let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero
self.content = .gift(gift, size)
} else if let messageIds = try container.decodeIfPresent([MessageId].self, forKey: .messageIds) {
let size = try container.decodeIfPresent(CGSize.self, forKey: .messageSize) ?? .zero
let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .messageFile)
let mediaRect = try container.decodeIfPresent(CGRect.self, forKey: .messageMediaRect)
let mediaCornerRadius = try container.decodeIfPresent(CGFloat.self, forKey: .messageMediaCornerRadius)
self.content = .message(messageIds, size, file, mediaRect, mediaCornerRadius)
} else if let _ = try container.decodeIfPresent(Bool.self, forKey: .dualVideo) {
let isAdditional = try container.decodeIfPresent(Bool.self, forKey: .isAdditionalVideo) ?? false
self.content = .dualVideoReference(isAdditional)
} else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .file) {
let fileType: Content.FileType
if let reaction = try container.decodeIfPresent(MessageReaction.Reaction.self, forKey: .reaction) {
var reactionStyle: Content.FileType.ReactionStyle = .white
if let style = try container.decodeIfPresent(Int32.self, forKey: .reactionStyle) {
reactionStyle = DrawingStickerEntity.Content.FileType.ReactionStyle(rawValue: style) ?? .white
}
fileType = .reaction(reaction, reactionStyle)
} else {
fileType = .sticker
}
self.content = .file(.standalone(media: file), fileType)
} else if let dataPath = try container.decodeIfPresent(String.self, forKey: .animatedImagePath), let data = try? Data(contentsOf: URL(fileURLWithPath: fullEntityMediaPath(dataPath))), let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let thumbnailImage = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
self.content = .animatedImage(data, thumbnailImage)
} else if let imagePath = try container.decodeIfPresent(String.self, forKey: .imagePath), let image = UIImage(contentsOfFile: fullEntityMediaPath(imagePath)) {
let isRectangle = try container.decodeIfPresent(Bool.self, forKey: .isRectangle) ?? false
let isDualPhoto = try container.decodeIfPresent(Bool.self, forKey: .isDualPhoto) ?? false
let imageType: Content.ImageType
if isDualPhoto {
imageType = .dualPhoto
} else if isRectangle {
imageType = .rectangle
} else {
imageType = .sticker
}
self.content = .image(image, imageType)
} else if let file = try container.decodeIfPresent(TelegramMediaFile.self, forKey: .videoFile) {
self.content = .video(file)
} else {
throw DrawingStickerEntity.DecodingError.generic
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
self.mirrored = try container.decode(Bool.self, forKey: .mirrored)
self.isExplicitlyStatic = try container.decodeIfPresent(Bool.self, forKey: .isExplicitlyStatic) ?? false
self.canCutOut = try container.decodeIfPresent(Bool.self, forKey: .canCutOut) ?? false
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
switch self.content {
case let .file(file, fileType):
try container.encode(file.media, forKey: .file)
switch fileType {
case let .reaction(reaction, reactionStyle):
try container.encode(reaction, forKey: .reaction)
try container.encode(reactionStyle.rawValue, forKey: .reactionStyle)
default:
break
}
case let .image(image, imageType):
let imagePath = "\(self.uuid).png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = image.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .imagePath)
}
switch imageType {
case .dualPhoto:
try container.encode(true, forKey: .isDualPhoto)
case .rectangle:
try container.encode(true, forKey: .isRectangle)
default:
break
}
case let .animatedImage(data, thumbnailImage):
let dataPath = "\(self.uuid).heics"
let fullDataPath = fullEntityMediaPath(dataPath)
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? data.write(to: URL(fileURLWithPath: fullDataPath))
try container.encodeIfPresent(dataPath, forKey: .animatedImagePath)
let imagePath = "\(self.uuid).png"
let fullImagePath = fullEntityMediaPath(imagePath)
if let imageData = thumbnailImage.pngData() {
try? FileManager.default.createDirectory(atPath: entitiesPath(), withIntermediateDirectories: true)
try? imageData.write(to: URL(fileURLWithPath: fullImagePath))
try container.encodeIfPresent(imagePath, forKey: .imagePath)
}
case let .video(file):
try container.encode(file, forKey: .videoFile)
case let .dualVideoReference(isAdditional):
try container.encode(true, forKey: .dualVideo)
try container.encode(isAdditional, forKey: .isAdditionalVideo)
case let .message(messageIds, size, file, mediaRect, mediaCornerRadius):
try container.encode(messageIds, forKey: .messageIds)
try container.encode(size, forKey: .messageSize)
try container.encodeIfPresent(file, forKey: .messageFile)
try container.encodeIfPresent(mediaRect, forKey: .messageMediaRect)
try container.encodeIfPresent(mediaCornerRadius, forKey: .messageMediaCornerRadius)
case let .gift(gift, size):
try container.encode(gift, forKey: .gift)
try container.encode(size, forKey: .messageSize)
}
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
try container.encode(self.mirrored, forKey: .mirrored)
try container.encode(self.isExplicitlyStatic, forKey: .isExplicitlyStatic)
try container.encode(self.canCutOut, forKey: .canCutOut)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
if let renderSubEntities = self.renderSubEntities {
let codableEntities: [CodableDrawingEntity] = renderSubEntities.compactMap { CodableDrawingEntity(entity: $0) }
try container.encode(codableEntities, forKey: .renderSubEntities)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingStickerEntity(content: self.content)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.scale = self.scale
newEntity.rotation = self.rotation
newEntity.mirrored = self.mirrored
newEntity.isExplicitlyStatic = self.isExplicitlyStatic
newEntity.canCutOut = self.canCutOut
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingStickerEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.content != other.content {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
if self.mirrored != other.mirrored {
return false
}
if self.isExplicitlyStatic != other.isExplicitlyStatic {
return false
}
if self.canCutOut != other.canCutOut {
return false
}
return true
}
}
public extension UIImage {
class func animatedImageFromData(data: Data) -> DrawingAnimatedImage? {
guard let source = CGImageSourceCreateWithData(data as CFData, nil) else {
return nil
}
let count = CGImageSourceGetCount(source)
var images = [UIImage]()
var duration = 0.0
for i in 0 ..< count {
if let cgImage = CGImageSourceCreateImageAtIndex(source, i, nil) {
let image = UIImage(cgImage: cgImage)
images.append(image)
let delaySeconds = UIImage.delayForImageAtIndex(Int(i), source: source)
duration += delaySeconds
}
}
return DrawingAnimatedImage(images: images, duration: duration)
}
class func delayForImageAtIndex(_ index: Int, source: CGImageSource!) -> Double {
var delay = 0.0
guard #available(iOS 13.0, *) else {
return delay
}
let cfProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil)
let gifPropertiesPointer = UnsafeMutablePointer<UnsafeRawPointer?>.allocate(capacity: 0)
if CFDictionaryGetValueIfPresent(cfProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSDictionary).toOpaque(), gifPropertiesPointer) == false {
return delay
}
let gifProperties:CFDictionary = unsafeBitCast(gifPropertiesPointer.pointee, to: CFDictionary.self)
var delayObject: AnyObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSUnclampedDelayTime).toOpaque()), to: AnyObject.self)
if delayObject.doubleValue == 0 {
delayObject = unsafeBitCast(CFDictionaryGetValue(gifProperties, Unmanaged.passUnretained(kCGImagePropertyHEICSDelayTime).toOpaque()), to: AnyObject.self)
}
delay = delayObject as? Double ?? 0
return delay
}
}
public final class DrawingAnimatedImage {
public let images: [UIImage]
public let duration: Double
init(images: [UIImage], duration: Double) {
self.images = images
self.duration = duration
}
}
@@ -0,0 +1,347 @@
import Foundation
import UIKit
import Display
import AccountContext
import TextFormat
public final class DrawingTextEntity: DrawingEntity, Codable {
final class CustomEmojiAttribute: Codable {
private enum CodingKeys: String, CodingKey {
case attribute
case rangeOrigin
case rangeLength
}
let attribute: ChatTextInputTextCustomEmojiAttribute
let range: NSRange
init(attribute: ChatTextInputTextCustomEmojiAttribute, range: NSRange) {
self.attribute = attribute
self.range = range
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.attribute = try container.decode(ChatTextInputTextCustomEmojiAttribute.self, forKey: .attribute)
let rangeOrigin = try container.decode(Int.self, forKey: .rangeOrigin)
let rangeLength = try container.decode(Int.self, forKey: .rangeLength)
self.range = NSMakeRange(rangeOrigin, rangeLength)
}
func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.attribute, forKey: .attribute)
try container.encode(self.range.location, forKey: .rangeOrigin)
try container.encode(self.range.length, forKey: .rangeLength)
}
}
private enum CodingKeys: String, CodingKey {
case uuid
case text
case textAttributes
case style
case animation
case font
case alignment
case fontSize
case color
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
case renderSubEntities
case renderAnimationFrames
}
public enum Style: Codable, Equatable {
case regular
case filled
case semi
case stroke
case blur
}
public enum Animation: Codable, Equatable {
case none
case typing
case wiggle
case zoomIn
}
public enum Font: Codable, Equatable {
case sanFrancisco
case other(String, String)
}
public enum Alignment: Codable, Equatable {
case left
case center
case right
}
public var uuid: UUID
public var isAnimated: Bool {
if self.animation != .none {
return true
}
var isAnimated = false
if let renderSubEntities = self.renderSubEntities {
for entity in renderSubEntities {
if entity.isAnimated {
isAnimated = true
break
}
}
}
return isAnimated
}
public struct TextAttributes {
public static let color = NSAttributedString.Key(rawValue: "Attribute__Color")
}
public var text: NSAttributedString
public var style: Style
public var animation: Animation
public var font: Font
public var alignment: Alignment
public var fontSize: CGFloat
public var color: DrawingColor
public var lineWidth: CGFloat = 0.0
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var width: CGFloat
public var scale: CGFloat
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public class AnimationFrame: Codable {
private enum CodingKeys: String, CodingKey {
case timestamp
case duration
case image
}
public let timestamp: Double
public let duration: Double
public let image: UIImage
public init(timestamp: Double, duration: Double, image: UIImage) {
self.timestamp = timestamp
self.duration = duration
self.image = image
}
required public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.timestamp = try container.decode(Double.self, forKey: .timestamp)
self.duration = try container.decode(Double.self, forKey: .duration)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .image) {
self.image = UIImage(data: renderImageData)!
} else {
fatalError()
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.timestamp, forKey: .timestamp)
try container.encode(self.duration, forKey: .duration)
if let data = self.image.pngData() {
try container.encode(data, forKey: .image)
}
}
}
public var renderAnimationFrames: [AnimationFrame]?
public init(text: NSAttributedString, style: Style, animation: Animation, font: Font, alignment: Alignment, fontSize: CGFloat, color: DrawingColor) {
self.uuid = UUID()
self.text = text
self.style = style
self.animation = animation
self.font = font
self.alignment = alignment
self.fontSize = fontSize
self.color = color
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
let text = try container.decode(String.self, forKey: .text)
let attributedString = NSMutableAttributedString(string: text)
let textAttributes = try container.decode([CustomEmojiAttribute].self, forKey: .textAttributes)
for attribute in textAttributes {
attributedString.addAttribute(ChatTextInputAttributes.customEmoji, value: attribute.attribute, range: attribute.range)
}
self.text = attributedString
self.style = try container.decode(Style.self, forKey: .style)
self.animation = try container.decode(Animation.self, forKey: .animation)
self.font = try container.decode(Font.self, forKey: .font)
self.alignment = try container.decode(Alignment.self, forKey: .alignment)
self.fontSize = try container.decode(CGFloat.self, forKey: .fontSize)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
if let renderSubEntities = try? container.decodeIfPresent([CodableDrawingEntity].self, forKey: .renderSubEntities) {
self.renderSubEntities = renderSubEntities.compactMap { $0.entity as? DrawingStickerEntity }
}
self.renderAnimationFrames = try container.decodeIfPresent([AnimationFrame].self, forKey: .renderAnimationFrames)
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.text.string, forKey: .text)
var textAttributes: [CustomEmojiAttribute] = []
self.text.enumerateAttributes(in: NSMakeRange(0, self.text.length), options: [], using: { attributes, range, _ in
if let value = attributes[ChatTextInputAttributes.customEmoji] as? ChatTextInputTextCustomEmojiAttribute {
textAttributes.append(CustomEmojiAttribute(attribute: value, range: range))
}
})
try container.encode(textAttributes, forKey: .textAttributes)
try container.encode(self.style, forKey: .style)
try container.encode(self.animation, forKey: .animation)
try container.encode(self.font, forKey: .font)
try container.encode(self.alignment, forKey: .alignment)
try container.encode(self.fontSize, forKey: .fontSize)
try container.encode(self.color, forKey: .color)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
if let renderSubEntities = self.renderSubEntities {
let codableEntities: [CodableDrawingEntity] = renderSubEntities.compactMap { CodableDrawingEntity(entity: $0) }
try container.encode(codableEntities, forKey: .renderSubEntities)
}
if let renderAnimationFrames = self.renderAnimationFrames {
try container.encode(renderAnimationFrames, forKey: .renderAnimationFrames)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingTextEntity(text: self.text, style: self.style, animation: self.animation, font: self.font, alignment: self.alignment, fontSize: self.fontSize, color: self.color)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingTextEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.text != other.text {
return false
}
if self.style != other.style {
return false
}
if self.animation != other.animation {
return false
}
if self.font != other.font {
return false
}
if self.alignment != other.alignment {
return false
}
if self.fontSize != other.fontSize {
return false
}
if self.color != other.color {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.width != other.width {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
return true
}
}
public extension DrawingTextEntity {
func setColor(_ color: DrawingColor, range: NSRange) {
if range.length == 0 {
self.color = color
let updatedText = self.text.mutableCopy() as! NSMutableAttributedString
let range = NSMakeRange(0, updatedText.length)
updatedText.removeAttribute(DrawingTextEntity.TextAttributes.color, range: range)
self.text = updatedText
} else {
let updatedText = self.text.mutableCopy() as! NSMutableAttributedString
updatedText.removeAttribute(DrawingTextEntity.TextAttributes.color, range: range)
updatedText.addAttribute(DrawingTextEntity.TextAttributes.color, value: color.toUIColor(), range: range)
self.text = updatedText
}
}
func color(in range: NSRange) -> DrawingColor {
if range.length == 0 {
return self.color
} else {
if let color = self.text.attribute(DrawingTextEntity.TextAttributes.color, at: range.location, effectiveRange: nil) as? UIColor {
return DrawingColor(color: color)
} else {
return self.color
}
}
}
}
@@ -0,0 +1,147 @@
import Foundation
import UIKit
import Display
import AccountContext
public final class DrawingVectorEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case type
case color
case lineWidth
case drawingSize
case referenceDrawingSize
case start
case mid
case end
case renderImage
}
public enum VectorType: Codable {
case line
case oneSidedArrow
case twoSidedArrow
}
public var uuid: UUID
public let isAnimated: Bool
public var type: VectorType
public var color: DrawingColor
public var lineWidth: CGFloat
public var drawingSize: CGSize
public var referenceDrawingSize: CGSize
public var start: CGPoint
public var mid: (CGFloat, CGFloat)
public var end: CGPoint
public var center: CGPoint {
return self.start
}
public var scale: CGFloat = 1.0
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(type: VectorType, color: DrawingColor, lineWidth: CGFloat) {
self.uuid = UUID()
self.isAnimated = false
self.type = type
self.color = color
self.lineWidth = lineWidth
self.drawingSize = .zero
self.referenceDrawingSize = .zero
self.start = CGPoint()
self.mid = (0.5, 0.0)
self.end = CGPoint()
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.isAnimated = false
self.type = try container.decode(VectorType.self, forKey: .type)
self.color = try container.decode(DrawingColor.self, forKey: .color)
self.lineWidth = try container.decode(CGFloat.self, forKey: .lineWidth)
self.drawingSize = try container.decode(CGSize.self, forKey: .drawingSize)
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.start = try container.decode(CGPoint.self, forKey: .start)
let mid = try container.decode(CGPoint.self, forKey: .mid)
self.mid = (mid.x, mid.y)
self.end = try container.decode(CGPoint.self, forKey: .end)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.type, forKey: .type)
try container.encode(self.color, forKey: .color)
try container.encode(self.lineWidth, forKey: .lineWidth)
try container.encode(self.drawingSize, forKey: .drawingSize)
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.start, forKey: .start)
try container.encode(CGPoint(x: self.mid.0, y: self.mid.1), forKey: .mid)
try container.encode(self.end, forKey: .end)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingVectorEntity(type: self.type, color: self.color, lineWidth: self.lineWidth)
if copy {
newEntity.uuid = self.uuid
}
newEntity.drawingSize = self.drawingSize
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.start = self.start
newEntity.mid = self.mid
newEntity.end = self.end
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingVectorEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.type != other.type {
return false
}
if self.color != other.color {
return false
}
if self.lineWidth != other.lineWidth {
return false
}
if self.drawingSize != other.drawingSize {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.start != other.start {
return false
}
if self.mid.0 != other.mid.0 || self.mid.1 != other.mid.1 {
return false
}
if self.end != other.end {
return false
}
return true
}
}
@@ -0,0 +1,193 @@
import Foundation
import UIKit
import Display
import AccountContext
import TextFormat
import Postbox
import TelegramCore
public final class DrawingWeatherEntity: DrawingEntity, Codable {
private enum CodingKeys: String, CodingKey {
case uuid
case style
case color
case hasCustomColor
case emoji
case temperature
case icon
case referenceDrawingSize
case position
case width
case scale
case rotation
case renderImage
}
public enum Style: Codable, Equatable {
case white
case black
case transparent
case custom
}
public var uuid: UUID
public var isAnimated: Bool {
return false
}
public var style: Style
public var icon: TelegramMediaFile?
public var emoji: String
public var temperature: Double
public var color: DrawingColor = DrawingColor(color: .white) {
didSet {
if self.color.toUIColor().argb == UIColor.white.argb {
self.style = .white
self.hasCustomColor = false
} else {
self.style = .custom
self.hasCustomColor = true
}
}
}
public var hasCustomColor = false
public var lineWidth: CGFloat = 0.0
public var referenceDrawingSize: CGSize
public var position: CGPoint
public var width: CGFloat
public var scale: CGFloat {
didSet {
self.scale = min(2.5, self.scale)
}
}
public var rotation: CGFloat
public var center: CGPoint {
return self.position
}
public var renderImage: UIImage?
public var renderSubEntities: [DrawingEntity]?
public var isMedia: Bool {
return false
}
public init(emoji: String, emojiFile: TelegramMediaFile?, temperature: Double, style: Style) {
self.uuid = UUID()
self.emoji = emoji
self.icon = emojiFile
self.temperature = temperature
self.style = style
self.referenceDrawingSize = .zero
self.position = .zero
self.width = 100.0
self.scale = 1.0
self.rotation = 0.0
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.uuid = try container.decode(UUID.self, forKey: .uuid)
self.emoji = try container.decode(String.self, forKey: .emoji)
self.temperature = try container.decode(Double.self, forKey: .temperature)
self.style = try container.decode(Style.self, forKey: .style)
self.color = try container.decodeIfPresent(DrawingColor.self, forKey: .color) ?? DrawingColor(color: .white)
self.hasCustomColor = try container.decodeIfPresent(Bool.self, forKey: .hasCustomColor) ?? false
if let iconData = try container.decodeIfPresent(Data.self, forKey: .icon) {
self.icon = PostboxDecoder(buffer: MemoryBuffer(data: iconData)).decodeRootObject() as? TelegramMediaFile
}
self.referenceDrawingSize = try container.decode(CGSize.self, forKey: .referenceDrawingSize)
self.position = try container.decode(CGPoint.self, forKey: .position)
self.width = try container.decode(CGFloat.self, forKey: .width)
self.scale = try container.decode(CGFloat.self, forKey: .scale)
self.rotation = try container.decode(CGFloat.self, forKey: .rotation)
if let renderImageData = try? container.decodeIfPresent(Data.self, forKey: .renderImage) {
self.renderImage = UIImage(data: renderImageData)
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(self.uuid, forKey: .uuid)
try container.encode(self.emoji, forKey: .emoji)
try container.encode(self.temperature, forKey: .temperature)
try container.encode(self.style, forKey: .style)
try container.encode(self.color, forKey: .color)
try container.encode(self.hasCustomColor, forKey: .hasCustomColor)
var encoder = PostboxEncoder()
if let icon = self.icon {
encoder = PostboxEncoder()
encoder.encodeRootObject(icon)
let iconData = encoder.makeData()
try container.encode(iconData, forKey: .icon)
}
try container.encode(self.referenceDrawingSize, forKey: .referenceDrawingSize)
try container.encode(self.position, forKey: .position)
try container.encode(self.width, forKey: .width)
try container.encode(self.scale, forKey: .scale)
try container.encode(self.rotation, forKey: .rotation)
if let renderImage, let data = renderImage.pngData() {
try container.encode(data, forKey: .renderImage)
}
}
public func duplicate(copy: Bool) -> DrawingEntity {
let newEntity = DrawingWeatherEntity(emoji: self.emoji, emojiFile: self.icon, temperature: self.temperature, style: self.style)
if copy {
newEntity.uuid = self.uuid
}
newEntity.referenceDrawingSize = self.referenceDrawingSize
newEntity.position = self.position
newEntity.width = self.width
newEntity.scale = self.scale
newEntity.rotation = self.rotation
return newEntity
}
public func isEqual(to other: DrawingEntity) -> Bool {
guard let other = other as? DrawingWeatherEntity else {
return false
}
if self.uuid != other.uuid {
return false
}
if self.emoji != other.emoji {
return false
}
if self.temperature != other.temperature {
return false
}
if self.style != other.style {
return false
}
if self.color != other.color {
return false
}
if self.referenceDrawingSize != other.referenceDrawingSize {
return false
}
if self.position != other.position {
return false
}
if self.width != other.width {
return false
}
if self.scale != other.scale {
return false
}
if self.rotation != other.rotation {
return false
}
return true
}
}