Hi all I am working on Objective-C. my previous Question was How can I edit PDF files in an iOS application?
after a lot of googling I found out the following. display the pdf in UIWebView, extract the data using C/javascript and edit it. I am still not sure about this procedure. now what I have planned is
1) display the pdf
2) when user wants to edit the pdf I covert the pdf to text and allow him to edit it
3) tryin to save wil convert the content back to pdf.
is this a gud way to proceed?? im k with step 1. now how do i convert pdf--> text and text-->pdf.
thanks in advance
When you load a custom document type (doc, ppt, pdf, etc) into a UIWebView, the webview returns a nil HTML string, even via javascript. There's a few suggestions for extracting PDF text here.
But turning the string back into a PDF is different. If you want to retain the formatting of the original PDF, I'm rather sure that's impossible because NSAttributedString on iOS doesn't do much. But this will work for plain text or NSAttributedString, if its possible:
NSData *PDFDataFromString(NSString *str) {
NSMutableData *data = [NSMutableData data];
//Create an NSAttributedString for CoreText. If you find a way to translate
//PDF into an NSAttributedString, you can skip this step and simply use an
//NSAttributedString for this method's argument.
NSAttributedString* string = [[[NSAttributedString alloc] initWithString:str] autorelease];
//612 and 792 are the dimensions of the paper in pixels. (8.5" x 11")
CGRect paperRect = CGRectMake(0.0, 0.0, 612, 792);
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef) string);
CGSize requiredSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter, CFRangeMake(0, [string length]), NULL, CGSizeMake(paperRect.size.width - 144, 1e40), NULL);
//Subtract the top and bottom margins (72 and 72), so they aren't factored in page count calculations.
NSUInteger pageCount = ceill(requiredSize.height / (paperRect.size.height - 144));
CFIndex resumePageIndex = 0;
UIGraphicsBeginPDFContextToData(data, paperRect, nil);
for(NSUInteger i = 0; i < pageCount; i++)
{
//After calculating the required number of pages, break up the string and
//draw them into sequential pages.
UIGraphicsBeginPDFPage();
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGContextSaveGState (currentContext);
CGContextSetTextMatrix(currentContext, CGAffineTransformIdentity);
CGMutablePathRef framePath = CGPathCreateMutable();
//72 and 72 are the X and Y margins of the page in pixels.
CGPathAddRect(framePath, NULL, CGRectInset(paperRect, 72.0, 72.0));
CTFrameRef frameRef = CTFramesetterCreateFrame(framesetter, CFRangeMake(resumePageIndex, 0), framePath, NULL);
resumePageIndex += CTFrameGetVisibleStringRange(frameRef).length;
CGPathRelease(framePath);
CGContextTranslateCTM(currentContext, 0, paperRect.size.height);
CGContextScaleCTM(currentContext, 1.0, -1.0);
CTFrameDraw(frameRef, currentContext);
CFRelease(frameRef);
CGContextRestoreGState (currentContext);
}
CFRelease(framesetter);
UIGraphicsEndPDFContext();
return data;
}
Related
So I am currently working on a subclass of UIView which uses TBXML to parse a SVG XML files and strip the 'g' path instructions out. Then it passes these sets of instructions to SvgToBezier - a simple parser that takes the path instructions as input and returns a UIBezierPath object. Then it draws them in the drawRect method.
The paths render fine, but the problem is they don't line up with each other the way they're supposed to (they way they show up in google chrome, adobe illustrator, etc)
I know the SVGKit library allows parsing of complex svg files, however I want to write this myself so I can expand my understanding of the concepts involved.
First I'll show you my drawRect method, followed by an example SVG file, it's expected output, and it's actual output drawn on the iPhone in a 300x300 rect.
- (void)drawRect:(CGRect)rect
{
//getting the XML from set property
TBXMLElement *root = self.svgRoot.rootXMLElement;
//getting the rect tag, which is the background
TBXMLElement *rectColor = [TBXML childElementNamed:#"rect" parentElement:root];
//getting the first g tag
TBXMLElement *g1 = [TBXML childElementNamed:#"g" parentElement:root];
//and it's child path tag
TBXMLElement *path1 = [TBXML childElementNamed:#"path" parentElement:g1];
//getting the sibbling g tag
TBXMLElement *g2 = g1->nextSibling;
//and it's child path tag
TBXMLElement *path2 = [TBXML childElementNamed:#"path" parentElement:g2];
//create a context reference
CGContextRef ctx = UIGraphicsGetCurrentContext();
//flip the context so the origin points jive
CGContextTranslateCTM(ctx, 0, rect.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
//get fill color for first path
const char *str1 = [[TBXML valueOfAttributeNamed:#"fill" forElement:g1] cStringUsingEncoding:NSASCIIStringEncoding];
long x1 = strtol(str1+1, NULL, 16);
UIColor *rgb1 = UIColorFromRGB(x1);
//get fill color for second path
const char *str2 = [[TBXML valueOfAttributeNamed:#"fill" forElement:g2] cStringUsingEncoding:NSASCIIStringEncoding];
long x2 = strtol(str2+1, NULL, 16);
UIColor *rgb2 = UIColorFromRGB(x2);
//get fill color for rect background
const char *str3 = [[TBXML valueOfAttributeNamed:#"fill" forElement:rectColor] cStringUsingEncoding:NSASCIIStringEncoding];
long x3 = strtol(str3+1, NULL, 16);
UIColor *rgb3 = UIColorFromRGB(x3);
//turn them into CGColors
CGColorRef color1 = rgb1.CGColor;
CGColorRef color2 = rgb2.CGColor;
CGColorRef color3 = rgb3.CGColor;
//draw the rect background and fill it
CGContextSetFillColorWithColor(ctx, color3);
CGContextFillRect(ctx, CGRectMake(0, 0, rect.size.width, rect.size.height));
//make a bezier path from the first path tag using SvgToBezier
SvgToBezier *bezier1 = [[SvgToBezier alloc] initFromSVGPathNodeDAttr:[TBXML valueOfAttributeNamed:#"d" forElement:path1] rect:rect];
//make second bezier path
SvgToBezier *bezier2 = [[SvgToBezier alloc] initFromSVGPathNodeDAttr:[TBXML valueOfAttributeNamed:#"d" forElement:path2] rect:rect];
//set fill color for first bezier
CGContextSetFillColorWithColor(ctx, color1);
//draw it
UIBezierPath *bezierPath1 = bezier1.bezier;
CGContextAddPath(ctx, bezierPath1.CGPath);
CGContextDrawPath(ctx, kCGPathEOFill);
//second bezier
CGContextSetFillColorWithColor(ctx, color2);
UIBezierPath *bezierPath2 = bezier2.bezier;
CGContextAddPath(ctx, bezierPath2.CGPath);
CGContextDrawPath(ctx, kCGPathEOFill);
}
Here's the Raw SVG
Here's the Expected Drawing Result
And here's the Actual iPhone Output (screenshot from iOS Simulator)
I've been experimenting with all kinds of calculations and CGAffineTransforms.. left all that out of the code because it will just confuse things imo.
Thanks for any and all help!
Cheers
EDIT:
Including another case, just to illustrate the inconsistency (consistency?) of the problem.
Raw
Expected
Actual
Hey just wanted to let anyone who read this know, I haven't solved the issue and I don't plan on it. I'm using UIWebView instead. Safari seems to render SVGs just fine. I used an HTML wrapper to make the SVG scale to fit its viewport.
I like the UIWebView better anyway because it deems to draw faster than my method.
Cheers
I have heard that I can display a NSAttributedString using CoreText, can anyone say me how (The simplest way)?
Please, don't answer with CATextLayer or OHAttributedLabel.
I know that there are a lot of questions about this in this forum, but I haven't find the answer
Thanks!!
Simplest way? Something like this:
CGContextRef context = UIGraphicsGetCurrentContext();
// Flip the coordinate system
CGContextSetTextMatrix(context, CGAffineTransformIdentity);
CGContextTranslateCTM(context, 0, self.bounds.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// Create a path to render text in
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, self.bounds );
// An attributed string containing the text to render
NSAttributedString* attString = [[NSAttributedString alloc]
initWithString:...];
// create the framesetter and render text
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attString);
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,
CFRangeMake(0, [attString length]), path, NULL);
CTFrameDraw(frame, context);
// Clean up
CFRelease(frame);
CFRelease(path);
CFRelease(framesetter);
I think that the simplest way (using Core Text) is:
// Create the CTLine with the attributed string
CTLineRef line = CTLineCreateWithAttributedString(attrString);
// Set text position and draw the line into the graphics context called context
CGContextSetTextPosition(context, x, y);
CTLineDraw(line, context);
// Clean up
CFRelease(line);
Using a Framesetter is more efficient IF you are drawing lots of text but this is the method recommended by Apple if you just need to display a small amount of text (like a label) and doesn't require you to create a path or frame (since it is done for you automatically by CTLineDraw).
Starting from ios 6 you can do the following :
NSMutableParagraphStyle *paragrahStyle = [[NSMutableParagraphStyle alloc] init];
[paragrahStyle setLineSpacing:40];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragrahStyle range:NSMakeRange(0, [labelText length])];
cell.label.attributedText = attributedString ;
Now I am creating an application that works like 'iAnnotate PDF'
Till now I have completed reading and displaying pdf pages in UIView.
Even I am sucessful in getting TOC.
Now I want to edit PDFs, Editing includes marker, highlights and writing notes.
Now my question is how to edit? I mean do we need to create another pdf and over write it with new changes?
Or there is any way we can update single page in existing pdfs??
If yes how is that done?
If we are rewriting the entire pdf, wont that be creating overhead?
You can create/edit pdf's easily using CoreGraphics. Just create a rect defining the size, then create the context, push the context, call CFPDFContextBeginPage and start drawing like you would normally do...Here's an example:
CGRect mediaBox = CGRectMake(0, 0, 550, 800);
NSString *url = [path stringByExpandingTildeInPath];
CGContextRef ctx = CGPDFContextCreateWithURL((CFURLRef)[NSURL fileURLWithPath:url], &mediaBox, NULL);
UIGraphicsPushContext(ctx);
//Page1
CGPDFContextBeginPage(ctx, NULL);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -mediaBox.size.height);
//name and value are NSStrings
[name drawInRect:rectName withFont:fontName];
[value drawInRect:rectValue withFont:fontValue];
[[UIImage imageNamed:#"logo.png"] drawInRect:CGRectMake(8, 15, 91, 99)];
CGPDFContextEndPage(ctx);
UIGraphicsPopContext();
CFRelease(ctx);
UPDATE
You can copy the pages you require and then do the drawing over them like this:
CGPDFDocumentRef originalDoc = NULL;
CGContextRef pdfContext = NULL;
CGRect docRect = CGRectZero;
NSURL *originalURL = [[NSBundle mainBundle] URLForResource:#"test" withExtension:#"pdf"];
NSString *newPath = #"/Users/***/Desktop/new.pdf";
CFURLRef newURL = CFURLCreateWithFileSystemPath (NULL,
(CFStringRef)newPath,
kCFURLPOSIXPathStyle,
false);
//Original doc and it's dimesnions
originalDoc = CGPDFDocumentCreateWithURL((CFURLRef)originalURL);
CGPDFPageRef firstPage = CGPDFDocumentGetPage(originalDoc, 1);
if (firstPage == NULL) {
NSLog(#"This document has no pages..Exiting.");
}
docRect = CGPDFPageGetBoxRect(firstPage, kCGPDFCropBox);
NSLog(#"%#", NSStringFromRect(docRect));
//New doc context
if (newURL != NULL) {
pdfContext = CGPDFContextCreateWithURL(newURL, &docRect, NULL);
if (pdfContext == NULL) {
NSLog(#"Error creating context");
}
CFRelease(newURL);
} else {
NSLog(#"Error creating url");
}
And then copy each page individually. In this particular example, I am adding "Bates" (numbering) to the pages.
//Copy original to new, and write bates
size_t count = CGPDFDocumentGetNumberOfPages(originalDoc);
for (size_t pageNumber = 1; pageNumber <= count; pageNumber++) {
CGPDFPageRef originalPage = CGPDFDocumentGetPage(originalDoc, pageNumber);
CGContextBeginPage (pdfContext,nil);
CGContextSetRGBFillColor(pdfContext, 0, 0, 255, 0.1);
CGContextSetRGBStrokeColor(pdfContext, 0, 0, 255, 0.5);
// Draw a circle (filled)
CGContextFillEllipseInRect(pdfContext, CGRectMake(0, 0, 25, 25));
CGContextSaveGState(pdfContext);
//flip context due to different origins
CGContextTranslateCTM(pdfContext, 0.0, (docRect.size.height - (docRect.size.height * 0.80))/2);
CGContextScaleCTM(pdfContext, 1.0, 0.8);
//copy content of template page on the corresponding page in new file
CGContextDrawPDFPage(pdfContext, originalPage);
CGContextRestoreGState(pdfContext);
//flip context back
//CGContextTranslateCTM(pdfContext, 0.0, -(docRect.size.height - (docRect.size.height * 0.80))/2);
//CGContextScaleCTM(pdfContext, 1.0, 1.25);
CGContextSetRGBFillColor(pdfContext, 0, 0, 255, 0.1);
CGContextSetRGBStrokeColor(pdfContext, 0, 0, 255, 0.5);
// Draw a circle (filled)
CGContextFillEllipseInRect(pdfContext, CGRectMake(0, 0, 25, 25));
NSString *bate = [self generateStringForCount:(int)pageNumber-1];
int fontSize = 15;
CGContextSelectFont(pdfContext, "Helvetica", fontSize, kCGEncodingMacRoman);
CGContextSetTextDrawingMode(pdfContext, kCGTextFill);
CGContextSetTextPosition(pdfContext, 0.0f, round(fontSize / 4.0f));
CGContextShowText(pdfContext, [bate UTF8String], strlen([bate UTF8String]));
CGContextEndPage(pdfContext);
}
CFRelease(pdfContext);
Maybe this idea can work in iPhone I think there is no directly way we can edit a PDF but what we can do we can produce such functionality that iAnnotate app have i.e. drawing line and text and then just save the PDF as a picture page by page in which you will include the edited page too and then you will create the PDF again by images then I think so you will get the edited PDF.
Now with the latest release of iOS, it becomes really easy to edit PDF on your iPhone.
Open PDF on your iPhone (usually on Books/ Photos)
Click on the markup tool.
From the more option at the bottom, you can choose Text, Signature, and Magnifier.
You can also add a different colour to text. But you can not edit the already existing text. I recommend you to go for some best PDF editing software.
I m a newbie in iphone.
Currently m doing a project to create a pdf having tables.
I created that. I can even write the text in table cells.
But now the problem is the data will come dynamically from database. That is also not a big deal if i m using drawinrect function. But now i want to display a rich text i.e. The text can be bold or italic.
How to write such rich text in a bounded rectangle...???
I can have text in html format.
Any solution for this???
// Initialize an attributed string.
NSString *html = text;
NSData *data = [html dataUsingEncoding:NSUTF8StringEncoding];
// Create attributed string from HTML
NSAttributedString *string = [[NSAttributedString alloc] initWithHTML:data baseURL:nil documentAttributes:NULL];
// CGColorRef red = CGColorCreate(rgbColorSpace, components); CGColorSpaceRelease(rgbColorSpace);
// CFAttributedStringSetAttribute(attrString, CFRangeMake(0, 50),kCTForegroundColorAttributeName, red);
// Create the framesetter with the attributed string.
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString(string);
// Create the frame and draw it into the graphics context
CTFrameRef frame = CTFramesetterCreateFrame(framesetter,CFRangeMake(0, 0), path, NULL);
CFRelease(framesetter);
CTFrameDraw(frame, pdfContext);
CFRelease(frame);
This solved my issue :)
How is iBooks able to create PDF page thumbnails so fast on first load? I tried using CGContext functions to draw the page and then resize it to get a thumbnail. But this approach takes way to long. Is there an efficient way to get thumbnails of PDF pages?
Thanks in advance,
Anupam
First get all your PDF files Path in an array[here ie:pdfs].Then if you want to show all these PDF thumbnails in UICollectionView,just pass the index obtained from the collection view Delegate Method "CellForRowAtIndexPath" to the following,
-(UIImage *)GeneratingIcon:(int)index
{
NSURL* pdfFileUrl = [NSURL fileURLWithPath:[pdfs objectAtIndex:index]];
CGPDFDocumentRef pdf = CGPDFDocumentCreateWithURL((__bridge CFURLRef)pdfFileUrl);
CGPDFPageRef page;
CGRect aRect = CGRectMake(0, 0, 102, 141); // thumbnail size
UIGraphicsBeginImageContext(aRect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
UIImage* IconImage;
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, aRect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextSetGrayFillColor(context, 1.0, 1.0);
CGContextFillRect(context, aRect);
// Grab the first PDF page
page = CGPDFDocumentGetPage(pdf, 1);
CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page, kCGPDFMediaBox, aRect, 0, true);
// And apply the transform.
CGContextConcatCTM(context, pdfTransform);
CGContextDrawPDFPage(context, page);
// Create the new UIImage from the context
IconImage = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
UIGraphicsEndImageContext();
CGPDFDocumentRelease(pdf);
return IconImage;
}
Hope this would be fast enough to create thumbnail images for the PDF.
Disclaimer: I have not checked if this is faster or not.
There are some built in methods in ImageIO that are specialized in creating thumbnails. These methods should be optimized for creating thumbnails. You would need to add ImageIO.framework to your project and #import <ImageIO/ImageIO.h> in your code.
// Get PDF-data
NSData *pdfData = [NSData dataWithContentsOfURL:myFileURL];
// Get reference to the source
// NOTE: You are responsible for releasing the created image source
CGImageSourceRef imageSourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)pdfData, NULL);
// Configure how to create the thumbnail
// NOTE: You should change the thumbnail size depending on how large thumbnails you need.
// 512 pixels is probably way too big. Smaller sizes will be faster.
NSDictionary* thumbnailOptions =
#{(id)kCGImageSourceCreateThumbnailWithTransform: (id)kCFBooleanTrue,
(id)kCGImageSourceCreateThumbnailFromImageIfAbsent: (id)kCFBooleanTrue,
(id)kCGImageSourceThumbnailMaxPixelSize: #512}; // no more than 512 px wide or high
// Create thumbnail
// NOTE: You are responsible for releasing the created image
CGImageRef imageRef =
CGImageSourceCreateThumbnailAtIndex(imageSourceRef,
0, // index 0 of the source
(__bridge CFDictionaryRef)thumbnailOptions);
// Do something with the thumbnail ...
// Release the imageRef and imageSourceRef
CGImageRelease (imageRef);
CFRelease(imageSourceRef);