Skip to content

Commit

Permalink
Implement support for PDF annotations for library (lispkit pdf).
Browse files Browse the repository at this point in the history
  • Loading branch information
objecthub committed Dec 29, 2024
1 parent 252d59f commit bb05f75
Show file tree
Hide file tree
Showing 6 changed files with 695 additions and 106 deletions.
9 changes: 9 additions & 0 deletions Sources/LispKit/Compiler/EvalError.swift
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ public enum EvalError: Int, Hashable, Codable {
case invalidPDFDocGenerationOption
case unknownPDFAnnotationType
case invalidPDFBorder
case invalidPDFLineStyle
case invalidPDFIconType
case invalidPDFMarkupType

public var message: String {
switch self {
Expand Down Expand Up @@ -550,6 +553,12 @@ public enum EvalError: Int, Hashable, Codable {
return "unknown PDF annotation type: $0"
case .invalidPDFBorder:
return "invalid PDF border specifier: $0"
case .invalidPDFLineStyle:
return "invalid PDF line style: $0"
case .invalidPDFIconType:
return "invalid PDF text annotation icon type: $0"
case .invalidPDFMarkupType:
return "invalid PDF text markup type: $0"
}
}

Expand Down
26 changes: 20 additions & 6 deletions Sources/LispKit/Graphics/Color.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,21 @@ public struct Color: CustomExpr {
self.blue = blue
self.alpha = alpha
#elseif os(macOS)
self.red = nc.redComponent
self.green = nc.greenComponent
self.blue = nc.blueComponent
self.alpha = nc.alphaComponent
let cspace = nc.colorSpace
if cspace.colorSpaceModel != .rgb,
let nc = nc.usingColorSpace(.sRGB) ??
nc.usingColorSpace(.deviceRGB) ??
nc.usingColorSpace(.genericRGB) {
self.red = nc.redComponent
self.green = nc.greenComponent
self.blue = nc.blueComponent
self.alpha = nc.alphaComponent
} else {
self.red = nc.redComponent
self.green = nc.greenComponent
self.blue = nc.blueComponent
self.alpha = nc.alphaComponent
}
#endif
}

Expand All @@ -115,14 +126,17 @@ public struct Color: CustomExpr {

/// Return string representation for colors.
public var tagString: String {
if self.alpha < 1.0 {
if self.alpha == 0.0 {
return "color clear"
} else if self.alpha < 1.0 {
if self.red == self.green && self.red == self.blue {
return "color \(self.red) \(self.alpha)"
} else {
return "color \(self.red) \(self.green) \(self.blue) \(self.alpha)"
}
} else if self.red == self.green && self.red == self.blue {
return "color \(self.red)"
return self.red == 1.0 ? "color white"
: (self.red == 0.0 ? "color black" : "color \(self.red)")
} else {
return "color \(self.red) \(self.green) \(self.blue)"
}
Expand Down
18 changes: 18 additions & 0 deletions Sources/LispKit/Graphics/Drawing.swift
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,7 @@ public enum DrawingInstruction: CustomStringConvertible {
case attributedText(NSAttributedString, at: ObjectLocation)
case image(NSImage, ObjectLocation, operation: NSCompositingOperation, opacity: Double)
case page(PDFPage, PDFDisplayBox, NSRect)
case annotation(PDFAnnotation, PDFDisplayBox, NSRect)
case inline(Drawing)
case include(Drawing, clippedTo: Shape?)

Expand Down Expand Up @@ -458,6 +459,21 @@ public enum DrawingInstruction: CustomStringConvertible {
// Draw
page.draw(with: box, to: context)
context.restoreGState()
case .annotation(let annotation, let box, let rect):
if let page = annotation.page {
let context = context.cgContext
context.saveGState()
// PDF coordinate system is Y-flipped from Core Graphics
context.translateBy(x: rect.origin.x, y: rect.origin.y + rect.height)
// Apply the PDF's crop box transform
let bounds = page.bounds(for: box)
page.transform(context, for: box)
// Scale PDF to view size
context.scaleBy(x: rect.width / bounds.width, y: -rect.height / bounds.height)
// Draw
annotation.draw(with: box, in: context)
context.restoreGState()
}
case .include(let drawing, let clippingRegion):
drawing.draw(clippedTo: clippingRegion)
case .inline(let drawing):
Expand Down Expand Up @@ -513,6 +529,8 @@ public enum DrawingInstruction: CustomStringConvertible {
return "image"
case .page(_, _, _):
return "page"
case .annotation(_, _, _):
return "annotation"
case .inline(_):
return "inline"
case .include(_, clippedTo: _):
Expand Down
17 changes: 17 additions & 0 deletions Sources/LispKit/Graphics/Drawing_iOS.swift
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ public enum DrawingInstruction: CustomStringConvertible, @unchecked Sendable {
case attributedText(NSAttributedString, at: ObjectLocation)
case image(UIImage, ObjectLocation, operation: CGBlendMode, opacity: Double)
case page(PDFPage, PDFDisplayBox, CGRect)
case annotation(PDFAnnotation, PDFDisplayBox, CGRect)
case inline(Drawing)
case include(Drawing, clippedTo: Shape?)

Expand Down Expand Up @@ -512,6 +513,20 @@ public enum DrawingInstruction: CustomStringConvertible, @unchecked Sendable {
// Draw
page.draw(with: box, to: context)
context.restoreGState()
case .annotation(let annotation, let box, let rect):
if let page = annotation.page {
context.saveGState()
// PDF coordinate system is Y-flipped from Core Graphics
context.translateBy(x: rect.origin.x, y: rect.origin.y + rect.height)
// Apply the PDF's crop box transform
let bounds = page.bounds(for: box)
page.transform(context, for: box)
// Scale PDF to view size
context.scaleBy(x: rect.width / bounds.width, y: -rect.height / bounds.height)
// Draw
annotation.draw(with: box, in: context)
context.restoreGState()
}
case .include(let drawing, let clippingRegion):
drawing.draw(clippedTo: clippingRegion, in: context)
case .inline(let drawing):
Expand Down Expand Up @@ -567,6 +582,8 @@ public enum DrawingInstruction: CustomStringConvertible, @unchecked Sendable {
return "image"
case .page(_, _, _):
return "page"
case .annotation(_, _, _):
return "annotation"
case .inline(_):
return "inline"
case .include(_, clippedTo: _):
Expand Down
7 changes: 5 additions & 2 deletions Sources/LispKit/Graphics/Shape.swift
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,8 @@ public final class Shape: NativeObject {
return self.compile().bounds
}

/// Internal method for computing the `BezierPath` object from the shape definition.
func compile() -> BezierPath {
/// Method for computing the `BezierPath` object from the shape definition.
public func compile() -> BezierPath {
if let bezierPath = self.bezierPath {
return bezierPath
}
Expand Down Expand Up @@ -296,6 +296,7 @@ public enum ShapePrototype {
case arc(center: CGPoint, radius: Double, startAngle: Double, endAngle: Double, clockwise: Bool)
case glyphs(String, in: CGRect, font: Font, flipped: Bool)
case interpolated([CGPoint], method: InterpolationMethod)
case path(BezierPath)
case shape(Shape)
case transformed(Shape, Transformation)
case flipped(Shape, CGRect?, vertical: Bool, horizontal: Bool)
Expand Down Expand Up @@ -416,6 +417,8 @@ public enum ShapePrototype {
return bezierPath
case .interpolated(let points, let method):
return method.compile(points)
case .path(let bp):
return bp.mutableCopy() as! BezierPath
case .shape(let shape):
return shape.compileNew()
case .transformed(let shape, let transform):
Expand Down
Loading

0 comments on commit bb05f75

Please sign in to comment.