App design help needed

I’m designing an application, and part of the requirements are to support Mac, iOS and Windows. It also needs to be able to print, export as bitmap and export as PDF (vector) what is displayed on screen.

Using Xojo’s Graphics class, I can use the same code to display on screen, print and export as Bitmap, for all three OSs (when iOS comes and hopefully it includes printing). However I have to use a third party tool to create vector PDF (and so the results may be different), which I’m not expecting to be iOS compatible.

CoreGraphics, will do display, print, bitmap and vector PDF all from the same code, but is limited to Apple devices. Also I hate working with CoreText, which is what I’d have to use to draw text in CG).

I just can’t decide if I should drop Windows and stick with CoreGraphics, use Xojo’s graphics system and third party for PDF. I’d rather not have to maintain 3 different chunks of code for drawing.

Any suggestions?

Difficult…

A couple of points:

  • Who knows when Xojo for iOS will be available let alone useable.
  • How important is Windows for you? Do you really want to go there?
  • Will users notice small differences between screen and print? Not really likely.
  • Encapsulate what varies. And be prepared to change horses in mid-race.

HTH

I wonder if many would be interested in an open source pdf extension of the graphics class? I’ve looked over the pdf spec, but haven’t really gotten too deep. Seems like a direct export without read/edit support wouldn’t be toooo difficult. Or maybe it would be?

I am currently working on a PDF extension of the XOJO graphics class… just hit some “real-life” issues that have delayed its completion

Object2D is the challeging part :slight_smile:

Agreed, which is why I’m trying to keep my designs as flexible as possible in the hopes that it won’t be too much work when it comes.

At this point, I’m not that sure, I’d like to keep the option open as I think the app I’m designing would/could appeal to Windows users, and I’m trying to build the bulk of it using native Xojo classes.

Small differences, probably not, but it really depends on how how great the differences are. This is part of the reason why I was considering CoreGraphics, because then there would be no difference (as it’s all the same generation code).

Sound advice.

I figured out last night how to generate a PDF from a Xojo canvas object on the Mac, hoping it won’t be too dissimilar on iOS.

Which just leaves Windows, I know that MBS has the DynaPDF module, but that does require maintain two versions of the drawing code. So yes I’m interested in an open source PDF generator extension of the graphics class. I’m not planning to use Object 2D, just plain old drawing instructions.

maybe you’ve already considered this… make an interface representing your Graphics needs and a factory method that produces the right type based on OS. So if it’s Mac a MyMacGraphics is returned or a MyWindowsGraphics on Windows, but they’re both of the interface. One of them will simply call through, drawRect code is just drawRect. The others may have to do a bit of fudging to give the same results. Like maybe line thickness is handled differently on different OSes.

By having the factory return a corresponding OS graphics you can write a single drawing routine, no need to maintain separate chuncks of drawing code. For PDF you’ll need to pass some flag to the factory method (because its not OS based), or make another factory method just for PDF graphics. After that choice for PDF though you can draw to the graphics the same as any other. When iOS comes out you just need to make a MyIOSGraphics that will produce the same results as the others. You’ll maintain separate classes for each destination but drawing code is homogenous.

[code]Interface iMyGraphics
Sub drawRect(x, y, w, h)
End Interface

Class MyMacGraphics implements iMyGraphics
Sub drawRect(x, y, w, h)
g.drawRect(x, y, w, h) //call through
End Sub
End Class

Class MyWindowsGraphics implements iMyGraphics
Sub drawRect(x, y, w, h)
g.drawRect(x + g.PenWidth \ 2, y + g.PenHeight \ 2, w, h) //match behavior
End Sub
End Class

Class MyPDFGraphics implements iMyGraphics
//…
End Class

Module GraphicsFactory
Function NewGraphics(pic As Picture) As iMyGraphics
if TargetMacOS then
return new MyMacGraphics
elseif TargetWindowsOS then
return new MyWindowsGraphics
end
End Function
Function NewPDFGraphics(destination As MyPDFThing) As iMyGraphics
//…
End Function
End Module

//usage

dim pic As new Picture(w, h)
dim g As iMyGraphics = GraphicsFactory.NewGraphics(pic)
g.drawRect(10, 10, 10, 10)

…or maybe make the factory an extension method…

dim g As iMyGraphics = pic.MyGraphics[/code]

I didn’t illustrate how constructors might be hidden or Picture/PDF references passed in or stored. It’s basically just an interface that’s implemented to give identical results to different destination types.

I would use MBS Dyna PDF and Coregraphics plugins if you are going to build the same app across Windows and the Mac. The best way I see would be to build some wrapper classes to wrap the dyna plugins in some new clothing. This clothing would make the “mbs dyna” plugins act like the core graphics, code wise. Once the wrapper is complete the code could simulated core graphics on both the Mac along with windows. Sounds a little hard but it could work.

You’re whole suggestion is kind of where I’m heading… I’ve never used interfaces, so this could be a new learning experience for me too :slight_smile:

Why not just use DyanPDF for both, then? DynaPDF works very well on the Mac - arguably better than CG. - and this would allow one mechanism for both platforms…

Well Dyna PDF may have more features when it comes to creating PDF files. Coregraphics on the other hand is better designed when it comes to the power of postscript and with that the ability it brings when writing to the screen, an image or a printer port while using the same exact commands. Its also well designed but has a simple and clean interface. Dyna PDF is designed from a file centric perspective and has more over head then core graphics or most libraries that do postscript like drawing to the screen.

Ideally I’d like to keep it as wysiwyg as possible, which is why I’m trying to reduce the different graphics toolkits as possible. The beauty with the Mac is you can use one graphics toolkit to rule them all, display, print, bitmap images, PDF & EPS files. This way I get all this functions from one set of instructions and it will be as close to wysiwyg as possible.

I don’t know enough about Windows, but from what I’ve seen so far the first three can easily be done with the same code, but PDF & EPS all require you to roll your own (or purchase a library/plugin/dll).

While I’m still pissed at Apple for their recent Quicktime fiasco, I love Apple, because a lot of the time when I want to do something, they’ve already provided me with the tools.

Getting PDF on a Mac is actually very easy, even in Xojo. This is first gen code, so it’s a little rough around the edges. The function returns a string, if it’s empty, the function worked, otherwise it returns the error message.

With this code, you do all the drawing in your canvas using Xojo’s Graphics functions (no need for CG) and it handles the rest for you. If you want more control, then you need to look at NSPrintOperation.

[code] Shared Function exportCanvasAsPDF(inCanvas as canvas, inFile as folderitem) As string
Dim errorMessage as string

#if TargetCocoa then
// - To create a PDF on OS X from a Xojo canvas, we’re sending the “dataWithPDFInsideRect” message to the underlying NSView.
// In order to do that, we also need to figure out the ‘bounds’ of the NSView, which is different to the ‘frame’ or the Xojo dimensions.
// Then once we have the PDF data, it comes in the form of a NSData object, so we extract that data into a memoryBlock and then dump it to disk.

// - Get the 'bounds' from the underlying NSView.
Declare function getBounds lib "Cocoa" selector "bounds" ( NSViewHandle as integer ) as NSPrintInfo.NSRect
Dim r as NSPrintInfo.NSRect = getBounds( inCanvas.handle )

// - Now request the PDF data (NSData) from the NSView, passing in the bounds.
declare function dataWithPDFInsideRect lib "Cocoa" selector "dataWithPDFInsideRect:" ( NSViewHandle as integer, aRect as NSPrintINfo.NSRect ) as Ptr
Dim NSDataRef as Ptr = dataWithPDFInsideRect( inCanvas.handle, r )

if NSDataRef = nil then
  errorMessage = "Failed to get the PDFData"
  
else
  // - Now we have a NSData object, lets get the length or size of it.
  declare function getlength lib "Cocoa" selector "length" ( ref as Ptr ) as integer
  dim blockLength as double = getLength( NSDataRef )
  
  // - Create a memoryBlock with the same size as the NSData object
  Dim data as new MemoryBlock( blockLength )
  
  if data = nil or data.size = data.sizeUnknown or data.size = 0 then
    errorMessage = "Failed to allocate enough memory to prepare writing to disk"
    
  else
    // - Send the NSDataRef the "getBytes" message and give it our memoryBlock.
    declare sub getBytes lib "Cocoa" selector "getBytes:length:" ( ref as Ptr, bytes as Ptr, length as integer )
    getBytes( NSDataRef, data, blockLength )
    
    Try
      // - Create the binaryStream
      Dim bis as binaryStream = binarystream.create( inFile, true )
      
      // - if the file already exists, set it's length to zero so that we don'e have any extra data left over from before.
      if bis.length <> 0 then bis.length = 0
      
      // - Write the memoryBlock to disk, then close the stream.
      bis.write data.stringValue( 0, data.size )
      bis.close
      
      // - let the developer know that we completed this task.
      Return ""
      
    Catch err as RuntimeException
      
      // - If we encountered an error in this chunk, then we must notify the user.
      errorMessage = err.message
      
    End Try
  end if
end if

#else
errorMessage = “PDF export is not supported in this version of the application.”
#endif

Return errorMessage
End Function[/code]

That approach definitely won’t fly on Windows. On windows, the canvas is completely empty. The only thing that shows is what you draw in the Paint event, and then that is temporary. You can’t get it back out of the canvas.

Push the actual drawing down as low as possible. I “draw” into a Graphics-like class that just collects drawing commands. Then I render to the screen (or printer, or PDF, or Excel file, CSV, etc.). The actual rendering code may be dramatically different from one output format to another, but the main code is the same and all the details are hidden from view.

I want to say thank you for the example - it’s really helped me to understand the power of interfaces and how I can apply it to my project.

So I can create an interface with the function “drawNode” and then I can create the following classes.
graphicsRenderer
dynaRenderer
SVGRenderer

Then in my main code, all I need to do is to tell what ever renderer to ‘drawNode’ and then the underlying renderer will use it’s own instructions to draw the node.

However I could also do this subclassing, I can create a renderer class, with a method ‘drawNode’ and a event ‘drawNode’ then in each subclass add the code for each renderer.

Without being too familiar with Interfaces, what’s the advantage?

[quote=51757:@Tim Hare]That approach definitely won’t fly on Windows. On windows, the canvas is completely empty. The only thing that shows is what you draw in the Paint event, and then that is temporary. You can’t get it back out of the canvas.

Push the actual drawing down as low as possible. I “draw” into a Graphics-like class that just collects drawing commands. Then I render to the screen (or printer, or PDF, or Excel file, CSV, etc.). The actual rendering code may be dramatically different from one output format to another, but the main code is the same and all the details are hidden from view.[/quote]
Indeed, I understand it won’t work on Windows and I’ll need to do something else, Will’s suggestion has gotten me thinking about extracting the drawing to a higher level mechanism, so that it can be broken down for other graphics toolkits. Just like what you suggested.

Still not clear on why you’d suggest implementing two different pieces of machinery for a cross platform application. Your argument might be a reasonable one for a Mac-only app, but using CoreGraphics for theMac and DynaPDF for Windows increases complexity by quite a bit.

Ah, but with the code I posted, you don’t need to use CoreGraphics to generate PDFs anymore, it can be done from Xojo’s canvas (using xojo.graphics calls). So now that I no longer need CoreGraphics, I’m left with Xojo graphics for all platforms (one hopes this will be the case when iOS is out) and just a third party toolkit to generate PDF on Windows.

Which means that Mac users will continue to get WYSIWYG, and with Windows, I’ll do my best to get it as close as I can to WYSIWYG.

At least that’s my plan at the moment.

Anything that needs to be WYSIWYG, especially anything that needs to be edited/manipulated by the user in a wysiwyg manner, should be represented by a formal data structure which can be rendered to the screen. Keep your data separate from the display. Just my .02.

I had no idea a canvas can be used like this, very juicy. It seems weird though to have to have a Canvas to generate a PDF or file or print. Can you just make a freestanding NSView instance or does it have to be part of a Window/Canvas?

When I don’t want to limit what can be plugged in I use an interface. Right now there may be a common super for all the polymorphic classes and it seems redundant to add an interface, but it means later on you’re not tied to that super and can use a sublass from something else.

If you know you’ll always be using the one common super then an interface isn’t necessary, it can always be refactored in if you need it.

I’ve not tried it personally, but I think it should work!

I know it does mess with your mind, generating PDFs and even printing directly from a canvas, but I think I understand it better…

Cocoa really is an awesome framework.

[quote=51968:@Will Shank]When I don’t want to limit what can be plugged in I use an interface. Right now there may be a common super for all the polymorphic classes and it seems redundant to add an interface, but it means later on you’re not tied to that super and can use a subclass from something else.

If you know you’ll always be using the one common super then an interface isn’t necessary, it can always be refactored in if you need it.[/quote]

Okay, so lets say I create a ‘Serialization’ interface with several functions.
storeInDictionary
extractFromDictionary
getValue
setValue

I can then easily add this to any of my classes, thus giving them the easy ability to serialize. I wish I’d understood this two years ago!