2

I have a piece of ObjC code from the great VFR PDF Viewer on GIT. It is using CGPDFDictionaryGetString to get a pointer to a string from a PDF annotation. Then it uses some byte pointer conversion to get the final string. In Monotouch there is no CGPDFDictionary.GetString() but only a .GetName() - this is the only method that returns a string, so I assumed that must be the correct method but it does not work. I can retrieve arrays, dictionaries, floats and integers just fine - only the strings don't seem to work.

See the small code examples below.

CGPDFStringRef uriString = NULL;
// This returns TRUE in the ObjC version and uriString is a valid pointer to a string.
if (CGPDFDictionaryGetString(actionDictionary, "URI", &uriString) == true)
{
  // Do some pointer magic - how to do this in MT? Do I have to at all?
  const char *uri = (const char *)CGPDFStringGetBytePtr(uriString);
  // *uri now contains a URL, I can see it in the debugger.
}

I translated it like that:

string sUri = null;
// This returns FALSE. Hence my sUri is NULL. Seems like GetName() is not the analogy to CGPDFDictionaryGetString.
if(oActionDic.GetName("URI", out sUri))
{
  // I never get here.
}

EDIT: Looking at the Mono sources I can see this in the Master branch: // TODO: GetString -> returns a CGPDFString

Switching to branch 4.2 reveals that it seems to be there. So I copied the code from there but have two issues:

  • I get an error about the "unsafe" keyword. It tells me to add the "unsafe" command line option. What is that and is it a good idea to add it? Where?
  • It seems to run anyway but the app hangs when getting the CGPDFString.

[DllImport (Constants.CoreGraphicsLibrary)] public extern static IntPtr CGPDFStringGetLength (IntPtr pdfStr);

    [DllImport (Constants.CoreGraphicsLibrary)]
    public extern static IntPtr CGPDFStringGetBytePtr (IntPtr pdfStr);

    public static string PdfStringToString (IntPtr pdfString)
    {
        if (pdfString == IntPtr.Zero)
            return null;

        int n = (int)CGPDFStringGetLength (pdfString);
        unsafe
        {
            return new String ((char *)CGPDFStringGetBytePtr (pdfString), 0, n);
        }
    }

    [DllImport (Constants.CoreGraphicsLibrary)]
    extern static bool CGPDFDictionaryGetString (IntPtr handle, string key, out IntPtr result);

    public static bool GetStringFromPdfDictionary (CGPDFDictionary oPdfDic, string key, out string result)
    {
        if (key == null)
            throw new ArgumentNullException ("key");
        IntPtr res;
        if (CGPDFDictionaryGetString (oPdfDic.Handle, key, out res))
        {
            result = PdfStringToString (res);
            return true;
        }
        result = null;
        return false;
    }
poupou
  • 43,413
  • 6
  • 77
  • 174
Krumelur
  • 32,180
  • 27
  • 124
  • 263

2 Answers2

1

If you use the unsafe keyword in your source then you need to enable unsafe when building your assembly. In MonoDevelop you can do this by:

  • Right-click on the project;
  • Select Options menu;
  • Select General icon;
  • Click the Allow 'unsafe' code checkbox
  • Click Ok button
  • Then rebuild.

Note: Your previous build should not have worked without this.

Source code between master and monotouch-4.2 should be identical in this case. I'll check but it's likely that you were looking at a specific revision in GIT (the was pushed before the code was updated). I'll check to be sure and edit the post.

UPDATE : This is the link to master (i.e. latest code available) and it shows:

public bool GetString (string key, out string result)

to be available. However it does depend on unsafe code (inside PdfStringToString) which you should not have been able to compile without allowing unsafe code in the assembly where you copy/pasted this code.

UPDATE2 : The value returned is UTF8 encoded so the string created from it needs to be decoded properly (another System.String constructor allows this). The above link to master should already point to the fixed version.

poupou
  • 43,413
  • 6
  • 77
  • 174
  • 1
    Meanwhile I submited a bug. It's 975. There is an issue with GetString(), to be more precise the line "new String ((char *)CGPDFStringGetBytePtr (pdfString), 0, n)" – Krumelur Sep 21 '11 at 21:31
  • I added an "unsafe" parameter in the options before seeing the checkbox. But it does not help. I copied the code from the master and get the same (incorrect) result as when using the monotouch.dll you provided for testing, so it is most probably not my code that's failing but a problem when converting the bytes to characters. – Krumelur Sep 21 '11 at 21:36
  • Thanks for filling the bug report. I'll look at it in a few hours (after dinner) and further findings (or a fix :-) to the bug report. – poupou Sep 21 '11 at 21:40
  • If you'll have a fixed DLL for me or a code fix that would be great. Enjoy your meal, I'll enjoy my bed now. – Krumelur Sep 21 '11 at 21:42
  • It's fixed. The returned buffer is UTF8 encoded and System.String must be aware of it to decode it properly. Rebuilding the 4.2 branch takes some time (since I work on master) but I'll update the bug with a newer binary once it's complete - but that could be 'my' tomorrow :) – poupou Sep 22 '11 at 00:45
0

I'm not a big fan of using unsafe blocks, and worked out a way to implement this method without using one. Initially I did try the unsafe style, however as the string is stored in UTF8 it needs to be converted.

private bool PDFDictionaryGetString (IntPtr handle, string key, out string result)
{
    IntPtr stringPtr;
    result = null;

    if (CGPDFDictionaryGetString(handle, "URI", out stringPtr)) {

        if (stringPtr == IntPtr.Zero)
                return false;

        // Get length of PDF String
        uint n = (uint) CGPDFStringGetLength (stringPtr);

        // Get the pointer of the string
        var ptr = CGPDFStringGetBytePtr (stringPtr);
        // Get the bytes
        var data = NSData.FromBytes(ptr, n);
        // Convert to UTF8
        var value = NSString.FromData(data, NSStringEncoding.UTF8);

        result = value.ToString();
        return true;
    }
    return false;
} 

There's a full blog post here and working sample including complete source here featuring multipage swipe navigation and clickable links.

Steve Davis
  • 297
  • 1
  • 2
  • 8