diagonal text with dirtyRect or BezierPath

I am trying to draw a diagonal text in a pdf using an attributed string’s AppKit’s instance drawInRect. I am, however, unable to draw a rotated text using dirtyRect or BezierPath.

I would appreciate any suggestions or links that might address methods to accompish this task.

Hi,

As I see, you should use PDFPage class’s draw method instead. Here is sample, written in Swift:


Abstract:
WatermarkPage is a PDFPage subclass that implements custom drawing.
*/

import Foundation
import PDFKit

/**
 WatermarkPage subclasses PDFPage so that it can override the draw(with box: to context:) method.
 This method is called by PDFDocument to draw the page into a PDFView. All custom drawing for a PDF
 page should be done through this mechanism.
 
 Custom drawing methods should always be thread-safe and call the super-class method. This is needed to draw the original PDFPage content. Custom drawing code can execute before or after this super-class call, though order matters! If your graphics run before the super-class call, they are drawn below the PDFPage content. Conversely, if your graphics run after the super-class call, they are drawn above the PDFPage.
*/

class WatermarkPage: PDFPage {

    // 3. Override PDFPage custom draw
    /// - Tag: OverrideDraw
    override func draw(with box: PDFDisplayBox, to context: CGContext) {

        // Draw original content
        super.draw(with: box, to: context)

        // Draw rotated overlay string
        UIGraphicsPushContext(context)
        context.saveGState()

        let pageBounds = self.bounds(for: box)
        context.translateBy(x: 0.0, y: pageBounds.size.height)
        context.scaleBy(x: 1.0, y: -1.0)
        context.rotate(by: CGFloat.pi / 4.0)

        let string: NSString = "U s e r   3 1 4 1 5 9"
        let attributes: [NSAttributedString.Key: Any] = [
            NSAttributedString.Key.foregroundColor: #colorLiteral(red: 0.4980392157, green: 0.4980392157, blue: 0.4980392157, alpha: 0.5),
            NSAttributedString.Key.font: UIFont.boldSystemFont(ofSize: 64)
        ]

        string.draw(at: CGPoint(x: 250, y: 40), withAttributes: attributes)

        context.restoreGState()
        UIGraphicsPopContext()

    }
}

Here is view controller sample in Swift:


/*
Abstract:
ViewController is the main view controller of DocumentWatermark. It drives the
 main PDFView display and loading of a PDFDocument.
*/

import UIKit
import PDFKit

/**
 This ViewController initializes a PDFDocument, sets its delegate to self, and implements
 the classForPage() delegate method. This declares that all instantiated PDFPages for
 the presented document (through PDFView) should instantiate the subclass WatermarkPage instead.
 This subclass, found in WatermarkPage.swift, implements custom drawing.
 
 ViewController first loads a path to our Sample.pdf file through the application's
 main bundle. This URL is then used to instantiate a PDFDocument. On success, the document
 is assigned to our PDFView, which was setup in InterfaceBuilder.
 
 Before document assignment, it is critical to assign our delegate, which is the ViewController
 itself, so that classForPage() (a PDFDocumentDelgetate method) implements classForPage().
 This method returns the PDFPage subclass used for custom drawing.
*/
class ViewController: UIViewController, PDFDocumentDelegate {

    @IBOutlet weak var pdfView: PDFView?

    /// - Tag: SetDelegate
    override func viewDidLoad() {
        super.viewDidLoad()

        if let documentURL = Bundle.main.url(forResource: "Sample", withExtension: "pdf") {
            if let document = PDFDocument(url: documentURL) {

                // Center document on gray background
                pdfView?.autoScales = true
                pdfView?.backgroundColor = UIColor.lightGray

                // 1. Set delegate
                document.delegate = self
                pdfView?.document = document
            }
        }
    }

    // 2. Return your custom PDFPage class
    /// - Tag: ClassForPage
    func classForPage() -> AnyClass {
        return WatermarkPage.self
    }

}

How does your Swift code translate into a script that can be called or adopted into Applescript?

You said: at least some information, but you yourself do not start the code, and instead you want a ready-made solution now. I am running out of time, so I will build the code, possibly intermittently.

I would start with this skeleton where the handler drawWaterMarkOn corresponds to the first draw handler from Swift code. We will not need the second Swift code handler, since we will initiate the PDF document ourselves:


use scripting additions
use framework "Foundation"
use framework "AppKit"
use framework "Quartz"
use framework "QuartzCore"
property mediabox : a reference to current application's kPDFDisplayBoxMediaBox
property |PI| : pi

-- Choose PDF, indicate destination path
set aPDF to choose file of type "pdf"
set destPosixPath to (POSIX path of (path to desktop folder)) & "WaterMarked.pdf"
-- Get PDF document
set aURL to (current application's |NSURL|'s fileURLWithPath:(POSIX path of aPDF))
set aPDFdoc to current application's PDFDocument's alloc()'s initWithURL:aURL
-- Count pages
set pCount to aPDFdoc's pageCount()

-- Process the pages
repeat with i from 0 to (pCount - 1)
	set pdfPage to (aPDFdoc's pageAtIndex:i)
	(my drawWaterMarkOn:pdfPage dpi:72)
end repeat

-- set outNSURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
-- aPDFdoc's writeToURL:outNSURL


----------------------- The Handler -----------------------------------
on drawWaterMarkOn:pdfPage dpi:theDPI
	-- Swift: let string: NSString = "U s e r 3 1 4 1 5 9"
	set watermarkText to current application's NSString's stringWithString:"U s e r 3 1 4 1 5 9"
	set scale to theDPI / 72
	-- Swift:  let pageBounds = self.bounds(for: box)
	set pageBounds to (pdfPage's boundsForBox:mediabox)
	set pageWidth to current application's NSWidth(pageBounds)
	set pageHeight to current application's NSHeight(pageBounds)
	set pixelWidth to (pageWidth * theDPI / 72) div 1
	set pixelHeight to (pageHeight * theDPI / 72) div 1
	-- make bitmaps
	set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(pdfPage's dataRepresentation()))
	set newRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
	-- Swift: context.saveGState()
	current application's NSGraphicsContext's saveGraphicsState()
	-- Swift: UIGraphicsPushContext(context)
	(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep))
	-- Swift: super.draw(with: box, to: context)
	(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
	(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep)'s rotateBy:(pi / 4.0)
end drawWaterMarkOn:dpi:

KniazidisR,
Thanks for your assistance. As I have never roated an image, I cannot debug the error the script throws at its last line of its handler

(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep)'s rotateBy:(pi / 4.0)

with

I cannot find any reference to rotateBy using applecript and frameworks Quartz or Foundation.

  1. Might you have ideas on a method to overcome this error?
    If not, my research of Rotation in Foundation appears to indicate a need for an affine matrix to transform.
  2. Do you know of a method to transform an affine matrix in this type of script, or do you prefer another method?

I suspect you need to use an affine transform, something like this:

use scripting additions
use framework "Foundation"
use framework "AppKit"
use framework "Quartz"
use framework "QuartzCore"

set theDpi to 72
set aPDF to choose file of type "pdf"
set destPosixPath to (POSIX path of (path to desktop folder)) & "WaterMarked.pdf"
-- prepare text stuff
set watermarkText to current application's NSString's stringWithString:"U s e r 3 1 4 1 5 9"
set theFont to current application's NSFont's fontWithName:"Helvetica" |size|:100
set theColor to current application's NSColor's blackColor()
set styleDict to current application's NSDictionary's dictionaryWithObjects:{theColor, theFont} forKeys:{current application's NSForegroundColorAttributeName, current application's NSFontAttributeName}
-- Get PDF stuff
set aURL to (current application's |NSURL|'s fileURLWithPath:(POSIX path of aPDF))
set aPDFdoc to current application's PDFDocument's alloc()'s initWithURL:aURL
set pCount to aPDFdoc's pageCount()
repeat with i from 0 to (pCount - 1)
	set thisPage to (aPDFdoc's pageAtIndex:i)
	set pageSize to (thisPage's boundsForBox:(current application's kPDFDisplayBoxMediaBox))
	set pageWidth to current application's NSWidth(pageSize)
	set pageHeight to current application's NSHeight(pageSize)
	set pixelWidth to (pageWidth * theDpi / 72) div 1
	set pixelHeight to (pageHeight * theDpi / 72) div 1
	-- make bitmaps
	set theImageRep to (current application's NSPDFImageRep's imageRepWithData:(thisPage's dataRepresentation()))
	set newRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)
	-- prepare transform
	set theTransform to current application's NSAffineTransform's transform()
	(theTransform's rotateByDegrees:45)
	-- do drawing
	current application's NSGraphicsContext's saveGraphicsState()
	(current application's NSGraphicsContext's setCurrentContext:(current application's NSGraphicsContext's graphicsContextWithBitmapImageRep:newRep))
	(theImageRep's drawInRect:{origin:{x:0, y:0}, |size|:{width:pixelWidth, height:pixelHeight}} fromRect:(current application's NSZeroRect) operation:(current application's NSCompositeSourceOver) fraction:1.0 respectFlipped:false hints:(missing value))
	theTransform's concat() -- applies transform
	(watermarkText's drawAtPoint:{0, 0} withAttributes:styleDict) -- point needs to allow for transform
	current application's NSGraphicsContext's restoreGraphicsState()
	-- make PDF page from bitmap
	(newRep's setSize:{pageWidth, pageHeight})
	set theData to newRep's TIFFRepresentation()
	set theImage to (current application's NSImage's alloc()'s initWithData:theData)
	set newPage to (current application's PDFPage's alloc()'s initWithImage:theImage)
	-- modify PDF doc 
	(aPDFdoc's removePageAtIndex:i)
	(aPDFdoc's insertPage:newPage atIndex:i)
end repeat
-- save
set outNSURL to current application's |NSURL|'s fileURLWithPath:destPosixPath
aPDFdoc's writeToURL:outNSURL

But honestly, I’d use one of the many apps that can do this. They avoid needing to rasterize, for starters.

Thanks for your help, Shane Stanley. I would have had to deal with this NSAffineTransform for a long time.

I tried to adjust the color to gray here, and to set alpha to 0.5. As I understand it, I have achieved transparency.


-- prepare text stuff
set watermarkText to current application's NSString's stringWithString:"      U s e r 3 1 4 1 5 9"
set theFont to current application's NSFont's fontWithName:"Helvetica" |size|:80
set theColor to current application's NSColor's grayColor()
set theColor to theColor's colorWithAlphaComponent:0.5
set styleDict to current application's NSDictionary's dictionaryWithObjects:{theColor, theFont} forKeys:{current application's NSForegroundColorAttributeName, current application's NSFontAttributeName}

I have 1 question.
You are talking about third-party applications: “They avoid needing to rasterize, for starters”. Is it because they draw watermark on the focused view?

You’d need:

set theColor to theColor's colorWithAlphaComponent:0.1

No, it’s because they can add the text directly as an element of the PDF itself.

Thanks for your great insights on [format]the colors colorWithAlphaComponent
NSAffineTransform’s transform.[/format] You have greatly increased my understanding of a Quartz method of generating graphics and overlaying text.

With the success of your script, I now note that the font in the output version is more blurred and less sharp, when compared with the font found in the original pdf document.
What might be some possibilities that reduce the font’s sharpness?
Might the loss of font sharpness arise from any of the following:
1.Tiff representation
2.NSBitmapImageRepresentation
3.Some other issue

Thanks for your help.

The sharpness will depend in part on the resolution you set. But you’re making bitmaps, and rotating text. See my earlier comment: I’d use one of the many apps that can do this.

My original pdf font Helvetica size 14 was transformed into a bolder and fuzzier-looking distortion.
My rotated font however appeared sharp without any apparent distortion.

Would not the opposite occur were bitmap rotation the cause of the font fuzziness?
Is it possible that the overlying rotated bitmap blurred the underlying bitmap font and that some other property was invoked to increase the boldness of that underlying font?

The applescript command:

set newNSBitmapImageRep to (current application's NSBitmapImageRep's alloc()'s initWithBitmapDataPlanes:(missing value) pixelsWide:pixelWidth pixelsHigh:pixelHeight bitsPerSample:8 samplesPerPixel:4 hasAlpha:yes isPlanar:false colorSpaceName:(current application's NSDeviceRGBColorSpace) bytesPerRow:0 bitsPerPixel:32)

returned:

1. What NSBitmapImageRep’ parameter can be altered or added to increase font sharpness or resolution in the bitmap image representation in which the original pdf was created?
2. What other Quartz object or parameter can be added to increase font sharpness or resolution in the original pdf font?
3. What property, if any, can be changed to allow greater penetration of the original document’s underlying font through the overlying bitmap?
4. What free programs do you recommend to circumvent this bitmap problem?

I greatly appreciate your continual insights.

Change the line:

set theDpi to 72

I can’t recommend any particular app.

Shane,
Thanks for your direction.

The variable, theDpi, already been set to 72, based upon your prior example. When I increased the size of theDPI, such as to 600, the clarity of the original PDF font increased back to its original clarity. Font resolution solved!
The page size however also grew. Ugh!
Reducing theDPI to 36 had the opposite effect of blurring the font and shrinking the page size. Worse!

What method might I use to increase the resulting quality of the Helvetica 14 font in the original document, without increasing the page size?

Using this approach, you can’t. You need to find an app that modifies the PDF without rasterizing it.

Is anti-aliasing available for this method, and if so, might it reduce the fuzziness in the rasterized font?

No and no.

I have set dots per inch to 300, rotated Transform by -45 degrees, scaled its x and y axes by 4, and translated its origin leftward by 300 and upward by 500.

set theTransform to current application's NSAffineTransform's transform()
	(theTransform's rotateByDegrees:-45)
	(theTransform's scaleXBy:4 yBy:4)
	(theTransform's translateXBy:(-300.0) yBy:500.0)

This has increased the original font sharpness.
As increasing the number of dots per inch variable, theDPI, to 300 enlarged the overall page size, I reduced the page size back to its original, or the size of a standard pdf, by employing the Print function, scaling to fit, and then saving to pdf.

What might be Applescript methods to perform the above print-related activity to:

  1. Reduce the page size back to its original, or to a standard pdf size?
  2. Scale it to fit its dimensions?
  3. Save it to a pdf?