Page 1 of 2

Simple PDF viewer

Posted: Wed Apr 20, 2011 9:59 pm
by Shardik
Have you ever had the need to display PDF documents in your own Mac application?
In contrast to Windows (where you have to buy 3rd party products like the high
priced Adobe Acrobat or other products or where you have to fight with the poorly
documented API of the Adobe Reader) MacOS X offers all the necessary API functions
to display PDF documents in your own applications. The following code example
demonstrates the basics of a very simple PDF viewer:

Code: Select all

EnableExplicit

ImportC ""
  CFStringCreateWithCString(CFAllocatorRef.L, CString, CFStringEncoding.L)
  CFRelease(CFTypeRef.L)
  CFURLCreateWithFileSystemPath(CFAllocatorRef.L, CFStringFilePath.L, PathStyle.L, IsDirectory.L)
  CGContextDrawPDFPage(CGContextRef.L, CGPDFPageRef.L)
  CGPDFDocumentCreateWithURL(CFURLRef.L)
  CGPDFDocumentGetNumberOfPages(PDFDocumentRef.L)
  CGPDFDocumentGetPage(PDFDocumentRef.L, PageNumber.L)
  CGPDFDocumentRelease(PDFDocumentRef.L)
  GetWindowPort(WindowRef.L)
  QDBeginCGContext(*ColorGraphicsPort, *CGContextRef)
  QDEndCGContext(*ColorGraphicsPort, *CGContextRef)
EndImport

#kCFAllocatorDefault = 0
#kCFStringEncodingMacRoman = 0
#kCFURLPOSIXPathStyle = 0

Procedure.L OpenPDFDocument(PathAndPDFFilename.S)
  Protected FilePathRef.L
  Protected PDFDocumentRef.L
  Protected URLRef.L

  ; ----- Convert PDF file path from PureBasic String into Core Foundation
  ;       string (CFString)
  FilePathRef = CFStringCreateWithCString(#kCFAllocatorDefault, @PathAndPDFFilename, #kCFStringEncodingMacRoman)

  If FilePathRef
    ; ----- Create CFURL object using CFString with local file system path
    URLRef = CFURLCreateWithFileSystemPath(#kCFAllocatorDefault, FilePathRef, #kCFURLPOSIXPathStyle, #False)
   
    If URLRef
      ; ----- Create Quartz PDF document with URL of PDF file
      PDFDocumentRef = CGPDFDocumentCreateWithURL(URLRef)
      CFRelease(URLRef)
    EndIf

    CFRelease(FilePathRef)
  EndIf

  ProcedureReturn PDFDocumentRef
EndProcedure

Procedure DrawPDFPage(PDFDocumentRef.L, PageNumber.L)
  Protected ContextRef.L
  Protected *GraphicsPort
  Protected PDFPageRef.L

  ; ----- Read page from Quartz PDF document
  PDFPageRef = CGPDFDocumentGetPage(PDFDocumentRef, PageNumber)
  
  If PDFPageRef
    ; ----- Obtain ColorGraphicsPort of window
    *GraphicsPort = GetWindowPort(WindowID(0))
    
    If *GraphicsPort
      ; ----- Obtain Quartz 2D drawing environment associated with graphics port
      QDBeginCGContext(*GraphicsPort, @ContextRef)
      
      If ContextRef
        ; ----- Draw PDF page into current graphics context
        CGContextDrawPDFPage(ContextRef, PDFPageRef)

        ; ----- Terminate Quartz 2D drawing environment
        QDEndCGContext(*GraphicsPort, @ContextRef)
      EndIf
    EndIf
  EndIf
EndProcedure

Define CurrentPage.L
Define LastPage.L
Define PathAndPDFFilename.S
Define PDFDocumentRef.L
Define WindowEvent.L

OpenWindow(0, 200, 50, 580, 790, "PDF-Viewer")
SetWindowColor(0, $FFFFFF)

; ----- Draw ToolBar buttons for one page back and forward
CreateImage(0,16,16)
StartDrawing(ImageOutput(0))
Box(0, 0, 16, 16, $FFFFFF)
DrawText(3, 0, "<", 0, $FFFFFF)
StopDrawing()
CreateImage(1,16,16)
StartDrawing(ImageOutput(1))
Box(0, 0, 16, 16, $FFFFFF)
DrawText(5, 0, ">", 0, $FFFFFF)
StopDrawing()
CreateToolBar(0, WindowID(0))
ToolBarImageButton(0,ImageID(0))
ToolBarImageButton(1,ImageID(1))

; ----- Create Quartz PDF object
PathAndPDFFilename = "/Developer/Documentation/CHUD/Saturn/SaturnUserGuide.pdf"
PDFDocumentRef = OpenPDFDocument(PathAndPDFFilename)

If PDFDocumentRef
  CurrentPage = 1
  LastPage = CGPDFDocumentGetNumberOfPages(PDFDocumentRef)
  DrawPDFPage(PDFDocumentRef, CurrentPage)

  Repeat
    WindowEvent = WaitWindowEvent()
    
    Select WindowEvent
      Case #PB_Event_Repaint
        DrawPDFPage(PDFDocumentRef, CurrentPage)
      Case #PB_Event_Menu
        Select EventMenu()
          Case 0
            If CurrentPage > 1
              CurrentPage - 1
            EndIf
          Case 1
            If CurrentPage < LastPage
              CurrentPage + 1
            EndIf
        EndSelect

        SetWindowColor(0, $FFFFFF)
        DrawPDFPage(PDFDocumentRef, CurrentPage)
    EndSelect
  Until WindowEvent = #PB_Event_CloseWindow

  ; ----- Release Quartz PDF document
  CGPDFDocumentRelease(PDFDocumentRef)
EndIf

Re: Simple PDF viewer

Posted: Wed Nov 23, 2011 8:07 pm
by WilliamL
Nice! Works fine here.

Code: Select all

; to load your own PDF
;PathAndPDFFilename = "/Developer/Documentation/CHUD/Saturn/SaturnUserGuide.pdf"
PathAndPDFFilename = OpenFileRequester("Load PDF file...", "", "", 0) ; pattern$ doesn't work on Mac
PDFDocumentRef = OpenPDFDocument(PathAndPDFFilename)
Thanks Shardik :)

@zabenka
Are you on a Mac? You don't need (or want) Acrobat Reader to get involved this, the PDF viewer runs in PureBasic.

Re: Simple PDF viewer

Posted: Fri Jun 14, 2013 5:43 am
by wilbert
I get missing symbols on x64.

Here's an alternative approach.
It's probably not as flexible but seems to work

Code: Select all

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz" : EndImport

Procedure PDFGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFGadgetCatchPDF(PDFGadget, *MemoryAddress, Size)
  Protected Viewer, Doc, PDFData
  PDFData = CocoaMessage(0, 0, "NSData dataWithBytes:", *MemoryAddress, "length:", Size)
  Doc = CocoaMessage(0, CocoaMessage(0, 0, "PDFDocument alloc"), "initWithData:", PDFData)
  If Doc
    Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
    CocoaMessage(0, Viewer, "setDocument:", Doc)
    CocoaMessage(0, Doc, "release")
  EndIf
EndProcedure

Procedure PDFGadgetLoadPDF(PDFGadget, Filename$)
  Protected PDFData = CocoaMessage(0, 0, "NSData dataWithContentsOfFile:$", @Filename$)
  If PDFData
    PDFGadgetCatchPDF(PDFGadget, CocoaMessage(0, PDFData, "bytes"), CocoaMessage(0, PDFData, "length"))
  EndIf
EndProcedure

Procedure.s PDFGadgetGetText(PDFGadget)
  Protected Result.s, Document = CocoaMessage(0, CocoaMessage(0, GadgetID(PDFGadget), "contentView"), "document")
  If Document
    Result = PeekS(CocoaMessage(0, CocoaMessage(0, Document, "string"), "UTF8String"), -1, #PB_UTF8)
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFThumbnailView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadgetSetPDFGadget(PDFThumbnailGadget, PDFGadget)
  Protected ThumbnailViewer = CocoaMessage(0, GadgetID(PDFThumbnailGadget), "contentView")
  Protected Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
  CocoaMessage(0, ThumbnailViewer, "setPDFView:", Viewer)
EndProcedure



If OpenWindow(0, 0, 0, 600, 400, "PDF Viewer example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  PDFGadget(0, 120, 10, 460, 300, #PB_Container_Flat)
  PDFThumbnailGadget(1, 10, 10, 100, 380, #PB_Container_Flat)
  EditorGadget(2, 120, 320, 460, 70, #PB_Editor_ReadOnly | #PB_Editor_WordWrap)
  PDFThumbnailGadgetSetPDFGadget(1, 0)
  PDFGadgetLoadPDF(0, "test.pdf")
  SetGadgetText(2, PDFGadgetGetText(0))
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf
Edit : I just notice this was a very old thread that came to the front due to a spam message. Well, maybe someone can use my code anyhow.

Re: Simple PDF viewer

Posted: Fri Jun 14, 2013 6:12 pm
by WilliamL
Hi wilbert!

Your code works fine here. I just wish there was a way to get the text from the displayed pdf. I was looking at your code and trying to figure out if the text could be copied out of the containers like we do with GetGadgetText().

I guess getting the text out of a pdf is pretty hard since all I see are programs that can do it are being sold for $s. I bought an app to read pdfs but I keep looking for a pb way to do it anyway.

Re: Simple PDF viewer

Posted: Fri Jun 14, 2013 6:47 pm
by wilbert
WilliamL wrote:I guess getting the text out of a pdf is pretty hard since all I see are programs that can do it are being sold for $s.
Actually it's easy (see updated example above).
If you don't need to display the PDF but only want its text, it's even easier

Code: Select all

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz" : EndImport

PDFFile.s = "test.pdf"
PDFDocument = CocoaMessage(0, CocoaMessage(0, 0, "PDFDocument alloc"), "initWithURL:", CocoaMessage(0, 0, "NSURL fileURLWithPath:$", @PDFFile))
PDFText.s = PeekS(CocoaMessage(0, CocoaMessage(0, PDFDocument, "string"), "UTF8String"), -1, #PB_UTF8)
CocoaMessage(0, PDFDocument, "release")
MessageRequester("", PDFText)

Re: Simple PDF viewer

Posted: Fri Jun 14, 2013 7:43 pm
by WilliamL
Hey wilbert!

That works! I like the way you modified your first example with the EditorGadget. :D

Now that I have the text and can play with it...

[later]
ok, it works fine with paragraphs but I get lists for columns (tables) a column at a time. It's pretty confusing. I think I read somewhere that the formatting of the text would be hosed.. and it is. It's too bad that the column elements don't have LFs (or some other delimiter) and CRs at the end of the lines.. but they don't. It's all CRs.

Ah, the 'devil is in the details'.

Re: Simple PDF viewer

Posted: Sat Jun 15, 2013 7:13 am
by wilbert
WilliamL wrote:ok, it works fine with paragraphs but I get lists for columns (tables) a column at a time. It's pretty confusing. I think I read somewhere that the formatting of the text would be hosed.. and it is. It's too bad that the column elements don't have LFs (or some other delimiter) and CRs at the end of the lines.. but they don't. It's all CRs.
It's possible to get it as html text but I don't know if that would make a difference.

Re: Simple PDF viewer

Posted: Wed Oct 02, 2013 6:55 pm
by spacebuddy
is there anyway to create a pdf file using text?

Re: Simple PDF viewer

Posted: Fri Jan 20, 2017 2:43 am
by coder14
Built in PDF? Marvelous! Thank you guys. :D

I tried both but can't get Shardik's code to work. It's lower level and more functions but fails at CGPDFDocumentCreateWithURL.

I modified and extended wilbert's code to load with "PDFDocument alloc initWithURL" and added the password and page functions and they work - SO FAR!

But when trying to get the dimensions of a page with boundsForBox: with any enumeration value the function fails with "boundsForBox: box is out of range".

What is causing this?

Re: Simple PDF viewer

Posted: Tue Nov 14, 2023 9:04 pm
by victorprogra
Hi,
Can't get this code to run, I would appreciate help:
PureBasic - Linker error:
clang: error: no such file or directory: '/System/Library/Frameworks/Quartz.framework/Quartz'

Running PureBasic 6.0.3 LTS on macOS Ventura 13.6


From:
wilbert post_id=414739 time=1371185023 user_id=1283

Code: Select all

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz" : EndImport

Procedure PDFGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFGadgetCatchPDF(PDFGadget, *MemoryAddress, Size)
  Protected Viewer, Doc, PDFData
  PDFData = CocoaMessage(0, 0, "NSData dataWithBytes:", *MemoryAddress, "length:", Size)
  Doc = CocoaMessage(0, CocoaMessage(0, 0, "PDFDocument alloc"), "initWithData:", PDFData)
  If Doc
    Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
    CocoaMessage(0, Viewer, "setDocument:", Doc)
    CocoaMessage(0, Doc, "release")
  EndIf
EndProcedure

Procedure PDFGadgetLoadPDF(PDFGadget, Filename$)
  Protected PDFData = CocoaMessage(0, 0, "NSData dataWithContentsOfFile:$", @Filename$)
  If PDFData
    PDFGadgetCatchPDF(PDFGadget, CocoaMessage(0, PDFData, "bytes"), CocoaMessage(0, PDFData, "length"))
  EndIf
EndProcedure

Procedure.s PDFGadgetGetText(PDFGadget)
  Protected Result.s, Document = CocoaMessage(0, CocoaMessage(0, GadgetID(PDFGadget), "contentView"), "document")
  If Document
    Result = PeekS(CocoaMessage(0, CocoaMessage(0, Document, "string"), "UTF8String"), -1, #PB_UTF8)
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFThumbnailView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadgetSetPDFGadget(PDFThumbnailGadget, PDFGadget)
  Protected ThumbnailViewer = CocoaMessage(0, GadgetID(PDFThumbnailGadget), "contentView")
  Protected Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
  CocoaMessage(0, ThumbnailViewer, "setPDFView:", Viewer)
EndProcedure



If OpenWindow(0, 0, 0, 600, 400, "PDF Viewer example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  PDFGadget(0, 120, 10, 460, 300, #PB_Container_Flat)
  PDFThumbnailGadget(1, 10, 10, 100, 380, #PB_Container_Flat)
  EditorGadget(2, 120, 320, 460, 70, #PB_Editor_ReadOnly | #PB_Editor_WordWrap)
  PDFThumbnailGadgetSetPDFGadget(1, 0)
  PDFGadgetLoadPDF(0, "test.pdf")
  SetGadgetText(2, PDFGadgetGetText(0))
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Re: Simple PDF viewer

Posted: Wed Nov 15, 2023 3:16 am
by DayDreamer
Not fancy but even less code, try just using WebGadget to load PDF file.

Re: Simple PDF viewer

Posted: Wed Nov 15, 2023 3:34 am
by mrbungle
try this:

Code: Select all

ImportC "-framework Quartz" : EndImport
victorprogra wrote: Tue Nov 14, 2023 9:04 pm Hi,
Can't get this code to run, I would appreciate help:
PureBasic - Linker error:
clang: error: no such file or directory: '/System/Library/Frameworks/Quartz.framework/Quartz'

Running PureBasic 6.0.3 LTS on macOS Ventura 13.6


From:
wilbert post_id=414739 time=1371185023 user_id=1283

Code: Select all

ImportC "/System/Library/Frameworks/Quartz.framework/Quartz" : EndImport

Procedure PDFGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFGadgetCatchPDF(PDFGadget, *MemoryAddress, Size)
  Protected Viewer, Doc, PDFData
  PDFData = CocoaMessage(0, 0, "NSData dataWithBytes:", *MemoryAddress, "length:", Size)
  Doc = CocoaMessage(0, CocoaMessage(0, 0, "PDFDocument alloc"), "initWithData:", PDFData)
  If Doc
    Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
    CocoaMessage(0, Viewer, "setDocument:", Doc)
    CocoaMessage(0, Doc, "release")
  EndIf
EndProcedure

Procedure PDFGadgetLoadPDF(PDFGadget, Filename$)
  Protected PDFData = CocoaMessage(0, 0, "NSData dataWithContentsOfFile:$", @Filename$)
  If PDFData
    PDFGadgetCatchPDF(PDFGadget, CocoaMessage(0, PDFData, "bytes"), CocoaMessage(0, PDFData, "length"))
  EndIf
EndProcedure

Procedure.s PDFGadgetGetText(PDFGadget)
  Protected Result.s, Document = CocoaMessage(0, CocoaMessage(0, GadgetID(PDFGadget), "contentView"), "document")
  If Document
    Result = PeekS(CocoaMessage(0, CocoaMessage(0, Document, "string"), "UTF8String"), -1, #PB_UTF8)
  EndIf
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadget(Gadget, x, y, Width, Height, Flags = 0)
  Protected Result, ContainerID, Viewer, Frame.NSRect
  Result = ContainerGadget(Gadget, x, y, Width, Height, Flags)
  CloseGadgetList()
  If Gadget = #PB_Any
    ContainerID = GadgetID(Result)
  Else
    ContainerID = GadgetID(Gadget)
  EndIf
  Viewer = CocoaMessage(0, CocoaMessage(0, 0, "PDFThumbnailView alloc"), "initWithFrame:@", @Frame)
  CocoaMessage(0, ContainerID, "setContentView:", Viewer)
  CocoaMessage(0, Viewer, "release")
  ProcedureReturn Result
EndProcedure

Procedure PDFThumbnailGadgetSetPDFGadget(PDFThumbnailGadget, PDFGadget)
  Protected ThumbnailViewer = CocoaMessage(0, GadgetID(PDFThumbnailGadget), "contentView")
  Protected Viewer = CocoaMessage(0, GadgetID(PDFGadget), "contentView")
  CocoaMessage(0, ThumbnailViewer, "setPDFView:", Viewer)
EndProcedure



If OpenWindow(0, 0, 0, 600, 400, "PDF Viewer example", #PB_Window_SystemMenu | #PB_Window_ScreenCentered)
  PDFGadget(0, 120, 10, 460, 300, #PB_Container_Flat)
  PDFThumbnailGadget(1, 10, 10, 100, 380, #PB_Container_Flat)
  EditorGadget(2, 120, 320, 460, 70, #PB_Editor_ReadOnly | #PB_Editor_WordWrap)
  PDFThumbnailGadgetSetPDFGadget(1, 0)
  PDFGadgetLoadPDF(0, "test.pdf")
  SetGadgetText(2, PDFGadgetGetText(0))
  Repeat : Until WaitWindowEvent() = #PB_Event_CloseWindow
EndIf

Re: Simple PDF viewer

Posted: Wed Nov 15, 2023 7:30 pm
by victorprogra
That did it, now it works perfect!
Thank you very much mrbungle :D

Man PDF viewer

Posted: Thu Nov 30, 2023 4:59 am
by Piero
mp.sh

Code: Select all

#!/bin/sh
man -t $1 > /tmp/$1.ps
open /tmp/$1.ps

Re: Simple PDF viewer

Posted: Thu Nov 30, 2023 8:07 am
by Fred
it's a PB forums, please post PureBasic code.