Skip to content

Commit

Permalink
feat: iOS text background padding setting
Browse files Browse the repository at this point in the history
  • Loading branch information
JimmyDaddy committed Sep 26, 2023
1 parent 4f49f3b commit 03eb4d1
Show file tree
Hide file tree
Showing 17 changed files with 346 additions and 44 deletions.
36 changes: 36 additions & 0 deletions .github/release-drafter.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name-template: 'v$RESOLVED_VERSION 🌈'
tag-template: 'v$RESOLVED_VERSION'
categories:
- title: '🚀 Features'
labels:
- 'feature'
- 'enhancement'
- 'feat'
- title: '🐛 Bug Fixes'
labels:
- 'fix'
- 'bugfix'
- 'bug'
- 'fixed'
- title: '🧰 Maintenance'
labels:
- 'chore'
- 'ci'
- 'refactor'
change-template: '- $TITLE @$AUTHOR (#$NUMBER)'
change-title-escapes: '\<*_&' # You can add # and @ to disable mentions, and add ` to disable code blocks.
version-resolver:
major:
labels:
- 'major'
minor:
labels:
- 'minor'
patch:
labels:
- 'patch'
default: patch
template: |
## Changes
$CHANGES
7 changes: 3 additions & 4 deletions example/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -462,8 +462,8 @@ function useViewModel() {
textBackgroundStyle: useTextBgStyle
? {
type: textBgStretch,
paddingX: 10,
paddingY: 10,
paddingBottom: '15%',
paddingRight: '10%',
color: '#0f0A',
}
: null,
Expand Down Expand Up @@ -495,8 +495,7 @@ function useViewModel() {
textBackgroundStyle: useTextBgStyle
? {
type: textBgStretch,
paddingX: 10,
paddingY: 10,
padding: '10%',
color: '#0fA',
}
: null,
Expand Down
13 changes: 13 additions & 0 deletions ios/RCTImageMarker/Constants.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Constants.swift
// react-native-image-marker
//
// Created by Jimmydaddy on 2023/8/10.
//

enum ErrorDomainEnum: String {
case PARAMS_REQUIRED = "com.jimmydaddy.imagemarker.PARAMS_REQUIRED"
case PARAMS_INVALID = "com.jimmydaddy.imagemarker.PARAMS_INVALID"
case BASE = "com.jimmydaddy.imagemarker"
}

23 changes: 14 additions & 9 deletions ios/RCTImageMarker/ImageMarker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
let className = "RCTImageLoader"
let classType: AnyClass? = NSClassFromString(className)
guard let imageLoader = self.bridge.module(for: classType) as? RCTImageLoader else {
throw NSError(domain: "com.jimmydaddy.imagemarker", code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to get ImageLoader module"])
throw NSError(domain: ErrorDomainEnum.BASE.rawValue, code: 1, userInfo: [NSLocalizedDescriptionKey: "Failed to get ImageLoader module"])
}
let images = try await withThrowingTaskGroup(of: (Int, UIImage).self) { group in
for (index, img) in imageOptions.enumerated() {
Expand All @@ -30,7 +30,7 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
if let image = UIImage.transBase64(img.uri) {
continuation.resume(returning: (index, image))
} else {
let error = NSError(domain: "com.jimmydaddy.imagemarker", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
let error = NSError(domain: ErrorDomainEnum.BASE.rawValue, code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
continuation.resume(throwing: error)
}
} else {
Expand All @@ -41,7 +41,7 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
} else if let error = error {
continuation.resume(throwing: error)
} else {
let error = NSError(domain: "com.jimmydaddy.imagemarker", code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
let error = NSError(domain: ErrorDomainEnum.BASE.rawValue, code: 3, userInfo: [NSLocalizedDescriptionKey: "Failed to load image"])
continuation.resume(throwing: error)
}
}
Expand Down Expand Up @@ -225,14 +225,19 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
}

if let textBackground = textOpts.style!.textBackground {
let bgEdgeInsets = textOpts.style?.textBackground?.toEdgeInsets(width: CGFloat(w), height: CGFloat(h))
context.setFillColor(textBackground.colorBg!.cgColor)
let stretchX = (bgEdgeInsets?.left ?? 0) + (bgEdgeInsets?.right ?? 0);
let stretchY = (bgEdgeInsets?.top ?? 0) + (bgEdgeInsets?.bottom ?? 0);
var bgRect = CGRect(x: CGFloat(CGFloat(posX) - (bgEdgeInsets?.left ?? 0)), y: CGFloat(CGFloat(posY) - (bgEdgeInsets?.top ?? 0)), width: size.width + stretchX, height: size.height + stretchY)
if textBackground.typeBg == "stretchX" {
context.fill(CGRect(x: 0, y: CGFloat(posY) - textBackground.paddingY, width: CGFloat(w), height: size.height + 2 * textBackground.paddingY))
bgRect = CGRect(x: 0, y: CGFloat(posY) - (bgEdgeInsets?.top ?? 0), width: CGFloat(w), height: size.height + stretchY)
} else if textBackground.typeBg == "stretchY" {
context.fill(CGRect(x: CGFloat(CGFloat(posX) - textBackground.paddingX), y: 0, width: size.width + 2 * textBackground.paddingX, height: CGFloat(h)))
} else {
context.fill(CGRect(x: CGFloat(CGFloat(posX) - textBackground.paddingX), y: CGFloat(CGFloat(posY) - textBackground.paddingY), width: size.width + 2 * textBackground.paddingX, height: size.height + 2 * textBackground.paddingY))
bgRect = CGRect(x: CGFloat(CGFloat(posX) - (bgEdgeInsets?.left ?? 0)), y: 0, width: size.width + stretchX, height: CGFloat(h))
}

bgRect.inset(by: bgEdgeInsets!)
context.fill(bgRect)
}

let rect = CGRect(origin: CGPoint(x: posX, y: posY), size: size)
Expand Down Expand Up @@ -353,7 +358,7 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
func mark(withText opts: [AnyHashable: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) -> Void {
let markOpts = MarkTextOptions.checkTextParams(opts, rejecter: rejecter)
if markOpts === nil {
rejecter("OPTS_INVALID", "opts invalid", nil)
rejecter(ErrorDomainEnum.PARAMS_INVALID.rawValue, "opts invalid", nil)
}
Task(priority: .userInitiated) {
do {
Expand All @@ -372,7 +377,7 @@ public final class ImageMarker: NSObject, RCTBridgeModule {
func mark(withImage opts: [AnyHashable: Any], resolver: @escaping RCTPromiseResolveBlock, rejecter: @escaping RCTPromiseRejectBlock) -> Void {
let markOpts = MarkImageOptions.checkImageParams(opts, rejecter: rejecter)
if markOpts === nil {
rejecter("OPTS_INVALID", "opts invalid", nil)
rejecter(ErrorDomainEnum.PARAMS_INVALID.rawValue, "opts invalid", nil)
}
Task(priority: .userInitiated) {
do {
Expand Down
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/ImageOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ImageOptions: NSObject {

init(dicOpts opts: [AnyHashable: Any]) throws {
guard let src = opts["src"] as? [AnyHashable: Any], !Utils.isNULL(src) else {
throw NSError(domain: "PARAMS_REQUIRED", code: 0, userInfo: [NSLocalizedDescriptionKey: "image is required"])
throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "image is required"])
}
self.src = src
self.uri = src["uri"] as! String
Expand Down
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/MarkImageOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class MarkImageOptions: Options {
let watermarkImageOpts = opts["watermarkImage"]
let watermarkImagesOpts = opts["watermarkImages"] as? [[AnyHashable: Any]]
if Utils.isNULL(watermarkImageOpts) && (Utils.isNULL(watermarkImagesOpts) || watermarkImagesOpts!.count <= 0) {
throw NSError(domain: "PARAMS_REQUIRED", code: 0, userInfo: [NSLocalizedDescriptionKey: "marker image is required"])
throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "marker image is required"])
}
if watermarkImagesOpts!.count > 0 {
self.watermarkImages = try watermarkImagesOpts!.map { try WatermarkImageOptions(dicOpts: $0) }
Expand Down
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/MarkTextOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class MarkTextOptions: Options {
override init(dicOpts opts: [AnyHashable: Any]) throws {
try super.init(dicOpts: opts)
guard let watermarkTextsOpts = opts["watermarkTexts"] as? [[AnyHashable: Any]], watermarkTextsOpts.count > 0 else {
throw NSError(domain: "PARAMS_REQUIRED", code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"])
throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"])
}
self.watermarkTexts = try watermarkTextsOpts.map { try TextOptions(dicOpts: $0) }
}
Expand Down
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/Options.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class Options: NSObject {

init(dicOpts opts: [AnyHashable: Any]) throws {
guard let backgroundImageOpts = opts["backgroundImage"] as? [AnyHashable: Any], !Utils.isNULL(backgroundImageOpts) else {
throw NSError(domain: "PARAMS_REQUIRED", code: 0, userInfo: [NSLocalizedDescriptionKey: "backgroundImage is required"])
throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "backgroundImage is required"])
}
self.backgroundImage = try ImageOptions(dicOpts: backgroundImageOpts)
self.quality = opts["quality"] as? Int ?? 100
Expand Down
135 changes: 135 additions & 0 deletions ios/RCTImageMarker/Padding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
//
// Padding.swift
// react-native-image-marker
//
// Created by Jimmydaddy on 2023/8/9.
//

import Foundation

class Padding {
var paddingTop: String = "0"
var paddingLeft: String = "0"
var paddingBottom: String = "0"
var paddingRight: String = "0"

init(paddingData: [AnyHashable: Any]) throws {
var topValue: String = "0"
var leftValue: String = "0"
var bottomValue: String = "0"
var rightValue: String = "0"

for (key, paddingValue) in paddingData {
switch key {
case "padding" as String:
if var paddingValue = paddingValue as? String {
paddingValue = paddingValue.trimmingCharacters(in: .whitespaces)
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 4) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
let values = paddingValue.components(separatedBy: " ")
if values.count == 1 {
topValue = values[0]
leftValue = values[0]
bottomValue = values[0]
rightValue = values[0]
} else if values.count == 2 {
topValue = values[0]
leftValue = values[1]
bottomValue = values[0]
rightValue = values[1]
} else if values.count == 3 {
topValue = values[0]
leftValue = values[1]
bottomValue = values[2]
rightValue = values[1]
} else if values.count == 4 {
topValue = values[0]
leftValue = values[1]
bottomValue = values[2]
rightValue = values[3]
}
break
} else if let paddingValue = paddingValue as? CGFloat {
topValue = String(format: "%f", paddingValue)
leftValue = String(format: "%f", paddingValue)
bottomValue = String(format: "%f", paddingValue)
rightValue = String(format: "%f", paddingValue)
}
case "paddingLeft" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
leftValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
leftValue = String(format: "%f", paddingValue)
}
case "paddingRight" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
rightValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
rightValue = String(format: "%f", paddingValue)
}
case "paddingTop" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
topValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
topValue = String(format: "%f", paddingValue)
}
case "paddingBottom" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
bottomValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
bottomValue = String(format: "%f", paddingValue)
}
case "paddingHorizontal" as String, "paddingX" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
rightValue = paddingValue;
leftValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
leftValue = String(format: "%f", paddingValue)
rightValue = String(format: "%f", paddingValue)
}
case "paddingVertical" as String, "paddingY" as String:
if let paddingValue = paddingValue as? String {
if !Utils.checkSpreadValue(str: paddingValue, maxLength: 1) {
throw NSError(domain: ErrorDomainEnum.PARAMS_INVALID.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "padding is invalid"])
}
topValue = paddingValue;
bottomValue = paddingValue;
} else if let paddingValue = paddingValue as? CGFloat {
topValue = String(format: "%f", paddingValue)
bottomValue = String(format: "%f", paddingValue)
}
default:
break
}
}

self.paddingTop = topValue
self.paddingLeft = leftValue
self.paddingBottom = bottomValue
self.paddingRight = rightValue
}

func toEdgeInsets(width: CGFloat, height: CGFloat) -> UIEdgeInsets {
let topValue = Utils.parseSpreadValue(v: self.paddingTop, relativeTo: height) ?? 0
let leftValue = Utils.parseSpreadValue(v: self.paddingLeft, relativeTo: width) ?? 0
let bottomValue = Utils.parseSpreadValue(v: self.paddingBottom, relativeTo: height) ?? 0
let rightValue = Utils.parseSpreadValue(v: self.paddingRight, relativeTo: width) ?? 0
return UIEdgeInsets(top: topValue, left: leftValue, bottom: bottomValue, right: rightValue)
}
}
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/RCTConvert+ImageMarker.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,6 @@ extension RCTConvert {
guard let value = value as? String, let mv = MyEnumMap[value] else {
return MarkerPositionEnum.topLeft
}
return mv ?? MarkerPositionEnum.none
return mv
}
}
9 changes: 3 additions & 6 deletions ios/RCTImageMarker/TextBackground.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ import Foundation
import UIKit
import React

class TextBackground: NSObject {
class TextBackground: Padding {
var typeBg: String?
var paddingX: CGFloat = 0.0
var paddingY: CGFloat = 0.0
var colorBg: UIColor?

init?(textBackgroundStyle textBackground: [AnyHashable: Any]?) {
init?(textBackgroundStyle textBackground: [AnyHashable: Any]?) throws {
guard let textBackground = textBackground, !Utils.isNULL(textBackground) else {
return nil
}
try super.init(paddingData: textBackground)
self.typeBg = textBackground["type"] as? String
self.paddingX = RCTConvert.cgFloat(textBackground["paddingX"])
self.paddingY = RCTConvert.cgFloat(textBackground["paddingY"])
self.colorBg = UIColor(hex: textBackground["color"] as! String) ?? UIColor.clear
}
}
2 changes: 1 addition & 1 deletion ios/RCTImageMarker/TextOptions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class TextOptions: NSObject {

init(dicOpts opts: [AnyHashable: Any]) throws {
guard let text = opts["text"] as? String else {
throw NSError(domain: "PARAMS_REQUIRED", code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"])
throw NSError(domain: ErrorDomainEnum.PARAMS_REQUIRED.rawValue, code: 0, userInfo: [NSLocalizedDescriptionKey: "text is required"])
}

if let positionOpts = opts["positionOptions"] as? [AnyHashable: Any] {
Expand Down
4 changes: 2 additions & 2 deletions ios/RCTImageMarker/TextStyle.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,14 @@ class TextStyle: NSObject {
var rotate: CGFloat = 0
var textAlign: String?

init(dicOpts opts: [AnyHashable: Any]) {
init(dicOpts opts: [AnyHashable: Any]) throws {
self.color = UIColor(hex: opts["color"] as! String) ?? UIColor.clear
if let shadowStyle = opts["shadowStyle"] as? [AnyHashable: Any] {
self.shadow = Utils.getShadowStyle(shadowStyle)
} else {
self.shadow = nil
}
self.textBackground = TextBackground(textBackgroundStyle: (opts["textBackgroundStyle"] as? [AnyHashable : Any]))
self.textBackground = try TextBackground(textBackgroundStyle: (opts["textBackgroundStyle"] as? [AnyHashable : Any]))
let scale = UIScreen.main.scale
let fontSize = opts["fontSize"] != nil ? (RCTConvert.cgFloat(opts["fontSize"]) * scale) : (14.0 * scale)
self.font = UIFont(name: opts["fontName"] as? String ?? "", size: fontSize)
Expand Down
20 changes: 20 additions & 0 deletions ios/RCTImageMarker/Utils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,4 +126,24 @@ class Utils: NSObject {
static func isNULL(_ obj: Any?) -> Bool {
return obj == nil || obj is NSNull
}

static func checkSpreadValue(str: String?, maxLength: Int = 1) -> Bool {
if str == nil { return false }
let pattern = #"^((\d+|\d+%)\s?){1,\#(maxLength)}$"#
if (str?.range(of: pattern, options: .regularExpression)) != nil {
return true
} else {
return false
}
}

static func parseSpreadValue(v: String?, relativeTo length: CGFloat) -> CGFloat? {
if v == nil { return nil }
if v?.hasSuffix(String(describing: "%")) ?? false {
let percent = CGFloat(Double(v!.dropLast()) ?? 0) / 100
return length * percent
} else {
return CGFloat(Double(v!) ?? 0)
}
}
}
Loading

0 comments on commit 03eb4d1

Please sign in to comment.