Skip to content

Commit 3ad7e18

Browse files
authored
Merge pull request #2495 from millenomi/nsintegralrectwithoptions
2 parents b3c96d4 + 675c1ae commit 3ad7e18

File tree

1 file changed

+175
-131
lines changed

1 file changed

+175
-131
lines changed

Foundation/NSGeometry.swift

+175-131
Original file line numberDiff line numberDiff line change
@@ -712,159 +712,203 @@ public func NSIntegralRect(_ aRect: NSRect) -> NSRect {
712712
return .zero
713713
}
714714

715-
return NSIntegralRectWithOptions(aRect, [.alignMinXOutward, .alignMaxXOutward, .alignMinYOutward, .alignMaxYOutward])
715+
var result: NSRect = .zero
716+
result.origin.x = CGFloat(floor(aRect.origin.x))
717+
result.origin.y = CGFloat(floor(aRect.origin.y))
718+
result.size.width = CGFloat(ceil(Double(aRect.origin.x) + Double(aRect.size.width)) - Double(result.origin.x))
719+
result.size.height = CGFloat(ceil(Double(aRect.origin.y) + Double(aRect.size.height)) - Double(result.origin.y))
720+
return result
716721
}
717-
public func NSIntegralRectWithOptions(_ aRect: NSRect, _ opts: AlignmentOptions) -> NSRect {
718-
let listOfOptionsIsInconsistentErrorMessage = "List of options is inconsistent"
719-
720-
if opts.contains(.alignRectFlipped) {
721-
NSUnimplemented()
722-
}
723722

724-
var width = CGFloat.NativeType.nan
725-
var height = CGFloat.NativeType.nan
726-
var minX = CGFloat.NativeType.nan
727-
var minY = CGFloat.NativeType.nan
728-
var maxX = CGFloat.NativeType.nan
729-
var maxY = CGFloat.NativeType.nan
723+
fileprivate func roundedTowardPlusInfinity(_ value: Double) -> Double {
724+
return floor(value + 0.5)
725+
}
730726

731-
if aRect.size.height.native < 0 {
732-
height = 0
733-
}
734-
if aRect.size.width.native < 0 {
735-
width = 0
736-
}
737-
727+
fileprivate func roundedTowardMinusInfinity(_ value: Double) -> Double {
728+
return ceil(value - 0.5)
729+
}
738730

739-
if opts.contains(.alignWidthInward) && width != 0 {
740-
guard width.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
741-
width = floor(aRect.size.width.native)
742-
}
743-
if opts.contains(.alignHeightInward) && height != 0 {
744-
guard height.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
745-
height = floor(aRect.size.height.native)
746-
}
747-
if opts.contains(.alignWidthOutward) && width != 0 {
748-
guard width.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
749-
width = ceil(aRect.size.width.native)
750-
}
751-
if opts.contains(.alignHeightOutward) && height != 0 {
752-
guard height.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
753-
height = ceil(aRect.size.height.native)
731+
fileprivate extension AlignmentOptions {
732+
var isAlignInward: Bool {
733+
return (rawValue & 0xFF) != 0
754734
}
755-
if opts.contains(.alignWidthNearest) && width != 0 {
756-
guard width.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
757-
width = round(aRect.size.width.native)
758-
}
759-
if opts.contains(.alignHeightNearest) && height != 0 {
760-
guard height.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
761-
height = round(aRect.size.height.native)
762-
}
763-
764735

765-
if opts.contains(.alignMinXInward) {
766-
guard minX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
767-
minX = ceil(aRect.origin.x.native)
768-
}
769-
if opts.contains(.alignMinYInward) {
770-
guard minY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
771-
minY = ceil(aRect.origin.y.native)
772-
}
773-
if opts.contains(.alignMaxXInward) {
774-
guard maxX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
775-
maxX = floor(aRect.origin.x.native + aRect.size.width.native)
776-
}
777-
if opts.contains(.alignMaxYInward) {
778-
guard maxY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
779-
maxY = floor(aRect.origin.y.native + aRect.size.height.native)
736+
var isAlignNearest: Bool {
737+
return (rawValue & 0xFF0000) != 0
780738
}
781-
782739

783-
if opts.contains(.alignMinXOutward) {
784-
guard minX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
785-
minX = floor(aRect.origin.x.native)
786-
}
787-
if opts.contains(.alignMinYOutward) {
788-
guard minY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
789-
minY = floor(aRect.origin.y.native)
790-
}
791-
if opts.contains(.alignMaxXOutward) {
792-
guard maxX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
793-
maxX = ceil(aRect.origin.x.native + aRect.size.width.native)
794-
}
795-
if opts.contains(.alignMaxYOutward) {
796-
guard maxY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
797-
maxY = ceil(aRect.origin.y.native + aRect.size.height.native)
740+
var minXOptions: AlignmentOptions {
741+
return intersection([.alignMinXInward, .alignMinXNearest, .alignMinXOutward])
798742
}
799743

800-
801-
if opts.contains(.alignMinXNearest) {
802-
guard minX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
803-
minX = round(aRect.origin.x.native)
804-
}
805-
if opts.contains(.alignMinYNearest) {
806-
guard minY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
807-
minY = round(aRect.origin.y.native)
744+
var maxXOptions: AlignmentOptions {
745+
return intersection([.alignMaxXInward, .alignMaxXNearest, .alignMaxXOutward])
808746
}
809-
if opts.contains(.alignMaxXNearest) {
810-
guard maxX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
811-
maxX = round(aRect.origin.x.native + aRect.size.width.native)
812-
}
813-
if opts.contains(.alignMaxYNearest) {
814-
guard maxY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
815-
maxY = round(aRect.origin.y.native + aRect.size.height.native)
747+
748+
var widthOptions: AlignmentOptions {
749+
return intersection([.alignWidthInward, .alignWidthNearest, .alignWidthOutward])
816750
}
817751

818-
var resultOriginX = CGFloat.NativeType.nan
819-
var resultOriginY = CGFloat.NativeType.nan
820-
var resultWidth = CGFloat.NativeType.nan
821-
var resultHeight = CGFloat.NativeType.nan
752+
var minYOptions: AlignmentOptions {
753+
return intersection([.alignMinYInward, .alignMinYNearest, .alignMinYOutward])
754+
}
822755

823-
if !minX.isNaN {
824-
resultOriginX = minX
756+
var maxYOptions: AlignmentOptions {
757+
return intersection([.alignMaxYInward, .alignMaxYNearest, .alignMaxYOutward])
825758
}
826-
if !width.isNaN {
827-
resultWidth = width
759+
760+
var heightOptions: AlignmentOptions {
761+
return intersection([.alignHeightInward, .alignHeightNearest, .alignHeightOutward])
828762
}
829-
if !maxX.isNaN {
830-
if width.isNaN {
831-
guard resultWidth.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
832-
resultWidth = maxX - minX
763+
}
764+
765+
fileprivate func integralizeRectAttribute(_ num: Double, options: AlignmentOptions, inward: (Double) -> Double, outward: (Double) -> Double, nearest: (Double) -> Double) -> Double {
766+
let tolerance: Double = (1.0 / Double(1 << 8))
767+
if options.isAlignNearest {
768+
let numTimesTwo = num * 2
769+
let roundedNumTimesTwo = roundedTowardPlusInfinity(numTimesTwo)
770+
if fabs(numTimesTwo - roundedNumTimesTwo) < 2 * tolerance {
771+
return nearest(roundedNumTimesTwo / 2)
833772
} else {
834-
guard resultOriginX.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
835-
resultOriginX = maxX - width
773+
return nearest(num)
836774
}
837-
}
838-
839-
840-
if !minY.isNaN {
841-
resultOriginY = minY
842-
}
843-
if !height.isNaN {
844-
resultHeight = height
845-
}
846-
if !maxY.isNaN {
847-
if height.isNaN {
848-
guard resultHeight.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
849-
resultHeight = maxY - minY
775+
} else {
776+
let roundedNum = roundedTowardPlusInfinity(num)
777+
if fabs(num - roundedNum) < tolerance {
778+
return roundedNum
850779
} else {
851-
guard resultOriginY.isNaN else { fatalError(listOfOptionsIsInconsistentErrorMessage) }
852-
resultOriginY = maxY - height
780+
if options.isAlignInward {
781+
return inward(num)
782+
} else {
783+
return outward(num)
784+
}
853785
}
854786
}
855-
856-
if resultOriginX.isNaN || resultOriginY.isNaN
857-
|| resultHeight.isNaN || resultWidth.isNaN {
858-
fatalError(listOfOptionsIsInconsistentErrorMessage)
787+
}
788+
789+
extension AlignmentOptions {
790+
func assertValid() {
791+
let inAttributes = rawValue & 0xFF
792+
let outAttributes = (rawValue & 0xFF00) >> 8
793+
let nearestAttributes = (rawValue & 0xFF0000) >> 16
794+
795+
let horizontal: AlignmentOptions = [.alignMinXInward, .alignMinXOutward, .alignMinXNearest, .alignMaxXInward, .alignMaxXOutward, .alignMaxXNearest, .alignWidthInward, .alignWidthOutward, .alignWidthNearest]
796+
let vertical: AlignmentOptions = [.alignMinYInward, .alignMinYOutward, .alignMinYNearest, .alignMaxYInward, .alignMaxYOutward, .alignMaxYNearest, .alignHeightInward, .alignHeightOutward, .alignHeightNearest]
797+
798+
if ((inAttributes & outAttributes) | (inAttributes & nearestAttributes) | (outAttributes & nearestAttributes)) != 0 {
799+
preconditionFailure("The options parameter is invalid. Only one of {in, out, nearest} may be set for a given rect attribute.")
800+
}
801+
802+
if intersection(horizontal).rawValue.nonzeroBitCount != 2 {
803+
preconditionFailure("The options parameter is invalid. There should be specifiers for exactly two out of {minX, maxX, width}.")
804+
}
805+
806+
if intersection(vertical).rawValue.nonzeroBitCount != 2 {
807+
preconditionFailure("The options parameter is invalid. There should be specifiers for exactly two out of {minY, maxY, height}.")
808+
}
859809
}
860-
861-
var result = NSRect.zero
862-
result.origin.x.native = resultOriginX
863-
result.origin.y.native = resultOriginY
864-
result.size.width.native = resultWidth
865-
result.size.height.native = resultHeight
866-
867-
return result
810+
}
811+
812+
public func NSIntegralRectWithOptions(_ aRect: NSRect, _ opts: AlignmentOptions) -> NSRect {
813+
opts.assertValid()
814+
815+
var integralRect: NSRect = .zero
816+
let horizontalEdgeNearest = roundedTowardPlusInfinity
817+
let verticalEdgeNearest = opts.contains(.alignRectFlipped) ? roundedTowardMinusInfinity : roundedTowardPlusInfinity
818+
819+
// two out of these three sets of options will have a single bit set:
820+
let minXOptions = opts.minXOptions
821+
let maxXOptions = opts.maxXOptions
822+
let widthOptions = opts.widthOptions
823+
824+
if minXOptions.isEmpty {
825+
// we have a maxX and a width
826+
integralRect.size.width = CGFloat(integralizeRectAttribute(Double(NSWidth(aRect)),
827+
options: widthOptions,
828+
inward: floor,
829+
outward: ceil,
830+
nearest: roundedTowardPlusInfinity))
831+
integralRect.origin.x = CGFloat(integralizeRectAttribute(Double(NSMaxX(aRect)),
832+
options: maxXOptions,
833+
inward: floor,
834+
outward: ceil,
835+
nearest: horizontalEdgeNearest)) - NSWidth(integralRect)
836+
} else if maxXOptions.isEmpty {
837+
// we have a minX and a width
838+
integralRect.origin.x = CGFloat(integralizeRectAttribute(Double(NSMinX(aRect)),
839+
options: minXOptions,
840+
inward: ceil,
841+
outward: floor,
842+
nearest: horizontalEdgeNearest))
843+
integralRect.size.width = CGFloat(integralizeRectAttribute(Double(NSWidth(aRect)),
844+
options: widthOptions,
845+
inward: floor,
846+
outward: ceil,
847+
nearest: roundedTowardPlusInfinity))
848+
} else {
849+
// we have a minX and a width
850+
integralRect.origin.x = CGFloat(integralizeRectAttribute(Double(NSMinX(aRect)),
851+
options: minXOptions,
852+
inward: ceil,
853+
outward: floor,
854+
nearest: horizontalEdgeNearest))
855+
integralRect.size.width = CGFloat(integralizeRectAttribute(Double(NSMaxX(aRect)),
856+
options: maxXOptions,
857+
inward: floor,
858+
outward: ceil,
859+
nearest: horizontalEdgeNearest)) - NSMinX(integralRect)
860+
}
861+
862+
// no negarects
863+
integralRect.size.width = max(integralRect.size.width, 0)
864+
865+
// two out of these three sets of options will have a single bit set:
866+
let minYOptions = opts.minYOptions
867+
let maxYOptions = opts.maxYOptions
868+
let heightOptions = opts.heightOptions
869+
870+
if minYOptions.isEmpty {
871+
// we have a maxY and a height
872+
integralRect.size.height = CGFloat(integralizeRectAttribute(Double(NSHeight(aRect)),
873+
options: heightOptions,
874+
inward: floor,
875+
outward: ceil,
876+
nearest: roundedTowardPlusInfinity))
877+
integralRect.origin.y = CGFloat(integralizeRectAttribute(Double(NSMaxY(aRect)),
878+
options: maxYOptions,
879+
inward: floor,
880+
outward: ceil,
881+
nearest: verticalEdgeNearest)) - NSHeight(integralRect)
882+
} else if maxYOptions.isEmpty {
883+
// we have a minY and a height
884+
integralRect.origin.y = CGFloat(integralizeRectAttribute(Double(NSMinY(aRect)),
885+
options: minYOptions,
886+
inward: ceil,
887+
outward: floor,
888+
nearest: verticalEdgeNearest))
889+
integralRect.size.height = CGFloat(integralizeRectAttribute(Double(NSHeight(aRect)),
890+
options: heightOptions,
891+
inward: floor,
892+
outward: ceil,
893+
nearest: roundedTowardPlusInfinity))
894+
} else {
895+
// we have a minY and a maxY
896+
integralRect.origin.y = CGFloat(integralizeRectAttribute(Double(NSMinY(aRect)),
897+
options: minYOptions,
898+
inward: ceil,
899+
outward: floor,
900+
nearest: verticalEdgeNearest))
901+
integralRect.size.height = CGFloat(integralizeRectAttribute(Double(NSMaxY(aRect)),
902+
options: maxYOptions,
903+
inward: floor,
904+
outward: ceil,
905+
nearest: verticalEdgeNearest)) - NSMinY(integralRect)
906+
}
907+
908+
// no negarects
909+
integralRect.size.height = max(integralRect.size.height, 0)
910+
911+
return integralRect
868912
}
869913

870914
public func NSUnionRect(_ aRect: NSRect, _ bRect: NSRect) -> NSRect {

0 commit comments

Comments
 (0)