[Résolu] Modification d'un fichier pdf existant

Alf1996Alf1996 Membre
décembre 2014 modifié dans API UIKit #1

Bonjour à  tous,


Voilà , je suis en train de développer une petite application qui doit permettre de rédiger des documents pdf à  partir de modèles. Je me suis donc plongée dans la documentation sur le sujet, et j'ai pas mal progressé, mais là  je suis en train de me noyer.


J'arrive à  créer un nouveau document pdf et à  y écrire ce que je veux, donc pour les méthodes qui permettent d'écrire un truc dans un nouveau pdf, tout va bien. Par contre, j'aimerais éviter de recréer l'ensemble du document à  chaque fois, et j'aimerais donc repartir d'un document existant (modèle).


 


Donc, je cherche la méthode (si elle existe) et sa syntaxe, pour ouvrir un document pdf existant et créer un context à  partir de ce pdf... Une petite piste ?


 


Merci d'avance


Mots clés:

Réponses

  • Bonjour,


     


    Il me semble que le plus simple est de réécrire ou redessiner "sur" l'original.


    Donc créer un nouveau pdf, dessiner l'original dedans, puis ce qui doit être ajouté.

  • Alf1996Alf1996 Membre
    août 2014 modifié #3

    Oui, je suis d'accord avec toi. Mon problème est que je ne sais pas récupérer le modèle...


    Je sais créer un nouveau fichier avec UIGraphicsBeginPDFContextToFile(pdfPath, CGRectZero, nil), je sais y écrire, créer des nouvelles pages, dessiner et importer des images dans ce nouveau pdf... mais je ne sais pas y copier mon modèle (qui se trouve dans le bundle). En fait, il me manque juste la méthode qui permet de faire çà  ! Et je dois avoir les neurones en court-circuit, mais je ne trouve pas çà  dans la doc !


    Merci de ton aide.


  • Alf1996Alf1996 Membre
    août 2014 modifié #4

    Bon, j'ai un peu avancé, mais ce n'est pas encore çà  ! Voici le code :



    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:@newFile.pdf];

    UIGraphicsBeginPDFContextToFile(pdfPath, CGRectZero, nil);

    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, kWidth, kHeight), nil);
    [self setupPDFDocumentNamed:@NewPDF Width:595 Height:842];
    [self beginPDFPage];
    CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("modele.pdf"), NULL, NULL);
    CGPDFDocumentRef pdfDocumentModele = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
    CFRelease(pdfURL);

    CGContextRef context = UIGraphicsGetCurrentContext();

    CGPDFPageRef page = CGPDFDocumentGetPage(pdfDocumentModele, 1);
    CGContextSaveGState(context);
    CGContextDrawPDFPage(context, page);
    CGContextRestoreGState(context);

    NSMutableParagraphStyle *paragraphStyleLeft = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
    paragraphStyleLeft.lineBreakMode = NSLineBreakByWordWrapping;
    paragraphStyleLeft.alignment = NSTextAlignmentLeft;
    NSDictionary *textAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:9.],
    NSParagraphStyleAttributeName: paragraphStyleLeft,
    NSForegroundColorAttributeName: [UIColor redColor]};
    [self addText:@Essai d'ajout d'un texte withFrame:CGRectMake(kLeftMargin, kTopMargin, kTextWidth, kTextHeight) withAttributes:textAttributes];

    UIGraphicsEndPDFContext();


    Avec la méthode addText :



    - (void)addText:(NSString*)text withFrame:(CGRect)frame withAttributes:(NSDictionary *)attributes {
    CGSize theSize=frame.size;

    CGSize stringSize = [text boundingRectWithSize:theSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil].size;

    float textWidth = frame.size.width;

    if (textWidth < stringSize.width)
    textWidth = stringSize.width;
    if (textWidth > _pageSize.width)
    textWidth = _pageSize.width - frame.origin.x;
    CGRect renderingRect = CGRectMake(frame.origin.x, frame.origin.y, textWidth, stringSize.height);

    [text drawInRect:renderingRect withAttributes:attributes];
    }


    Le problème, c'est que mon modèle s'affiche à  l'envers (comme si je le regardais dans un miroir... Alors évidemment, le texte que j'insère qui est lui dans la bon sens, n'est pas positionné au bon endroit !


     


    Qu'est-ce que je fais de travers ?  B)


     


    Merci d'avance.


  • AliGatorAliGator Membre, Modérateur
    août 2014 modifié #5
    Je sais que ça ne va pas faire avancer le schmilblick sur ta question du retournement, mais :

    1) Attention à  respecter la Create Rule. On a tendance à  l'oublier maintenant que le monde Cocoa/Objet fait tout tout seul avec ARC, mais dans le monde C, toujours balancer les fonctions XXXCreate() et XXXCopy() par un CFRelease(). Or dans ton code je vois entre autres CGPDFDocumentCreateWithURL(...) sans CFRelease associé, et de même pour un CFBundleCopyResourceURL.

    2) Je ne connais pas l'API de CGPDF, mais ça m'étonne un peu qu'un UIGraphicsBeginPDFPageWithInfo n'ait pas à  être balancé avec une fonction opposée du genre UIGraphicsEndPDFPage, comme la plupart des méthodes XXXBeginYYY sont en général balancées par un XXXEndYYY ?


    ---

    [EDIT]
    Sinon, pour ton problème de retournement, c'est très certainement une histoire de référenciel de coordonnées, certains contextes ayant leur origine en bas à  gauche et les Y croissants vers le haut (genre CoreGraphics), d'autres ont leur origine en haut à  gauche et les Y croissants vers le bas (genre UIKit).

    Du coup il faut certainement que tu appliques un CGAffineTransform à  ton CGContextRef pour le retourner (Scale(1,-1) pour inverser les Y + Translate(height) pour recaler l'origin, le seul truc c'est que je ne sais jamais dans quel sens mais bon à  tâtons on finit toujours par retrouver).

    Ce qui d'ailleurs donnerait une utilité à  tes appels à  CGContextSaveGState() et CGContextRestoreGState() qui sinon ne servent pas à  grand chose vu qu'entre ces 2 appels tu ne modifies pas le contexte dans ton code actuel (pas de transformation, ni de changement de strokeColor et/ou fillColor, etc). Alors qu'en rajoutant la transformation pour mettre ton contexte dans le bon sens avant de dessiner ton PDF modèle dedans, ça aura une utilité de sauver son état avant la transformation et restaurer son état après ;)
  • Désolé pour ce délai de réponse.


     


    Dans mon cas je dois stocker le pdf dans mon fichier donc je le charge ainsi :



    if ([fileType isEqualToString:@pdf]) {
    NSData *mData = [[NSData alloc] initWithContentsOfFile:path];
    CGDataProviderRef dataProvider = CGDataProviderCreateWithCFData((CFDataRef)mData);
    CGPDFDocumentRef pdf = CGPDFDocumentCreateWithProvider(dataProvider);
    CGPDFPageRef page1 = CGPDFDocumentGetPage(pdf, 1);
    CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
    mPicture.x2 = mPicture.x1 + mediaRect.size.width;
    mPicture.y2 = mPicture.y1 + mediaRect.size.height;
    [mPicture setMPdf:pdf];
    [mPicture setPdfData:mData];
    CGDataProviderRelease(dataProvider);
    //CGPDFDocumentRelease(pdf);
    [mData release];
    }

    où mPicture est un de mes propres objets de dessin.


     


    Puis pour dessiner le pdf :



    double ldx = (x2 - x1);
    double ldy = (y2 - y1);
    double ldxz = ldx * zoomFactor;
    double ldyz = ldy * zoomFactor;

    CGContextTranslateCTM(contex, 0.0, ldyz);
    CGContextTranslateCTM(contex, (x1 + ldx / 2 - delta_x) * zoomFactor, (y1 - ldy / 2 - delta_y) * zoomFactor); //origine = milieu image
    CGContextRotateCTM(contex, [self angle] / DegreSurRadian);

    CGPDFPageRef page1 = CGPDFDocumentGetPage(mPdf, 1);

    CGContextTranslateCTM(contex, - ldxz / 2, + ldyz / 2);
    CGRect mediaRect = CGPDFPageGetBoxRect(page1, kCGPDFCropBox);
    CGContextScaleCTM(contex, 1.0 * zoomFactor * ldx / mediaRect.size.width, -1.0 * zoomFactor * ldy / mediaRect.size.height);

    CGContextDrawPDFPage(contex, page1);

    CGContextScaleCTM(contex, 1.0 / zoomFactor / ldx * mediaRect.size.width, 1.0 / zoomFactor / ldy * mediaRect.size.height);


    x1, x2, y1 ,y2 sont les 2 coins opposés de l'image dans mon repère global.


    zoomFactor est le coefficient de zoom du dessin.


    Je ne dessine que la première page du pdf.


     


    Voilà .

  • Merci à  tous les deux. Je regarde ça ce soir, et je vous tiens au courant... Là  je suis sur l'iPhone ! ;)
  • Alf1996Alf1996 Membre
    août 2014 modifié #8

    Voilà  çà  y est, j'ai fini par réussir ce que je voulais faire. Merci à  tous les deux, vous m'avez mis sur la piste. 

     

    @AliGator : tu avais raison, il y avait bien une histoire de référentiel différent. Les pdf utilisent comme origine le bas de page à  gauche... et il fallait donc bien faire un changement de référentiel avant d'écrire le pdf, mais par contre, ce que je n'avais pas compris, c'est qu'il fallait que je remette le bon référentiel avant d'écrire mes propres textes !  B)

     

    Voilà  ce que çà  donne, çà  pourra surement aider quelqu'un un jour :



    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES);

    NSString *documentsDirectory = [paths objectAtIndex:0];

    NSString *pdfPath = [documentsDirectory stringByAppendingPathComponent:@newFile.pdf];

    UIGraphicsBeginPDFContextToFile(pdfPath, CGRectZero, nil);

    UIGraphicsBeginPDFPageWithInfo(CGRectMake(0, 0, _pageSize.width, _pageSize.height), nil);



    CGContextRef context = UIGraphicsGetCurrentContext();

    CGContextSaveGState(context);

    NSMutableParagraphStyle *paragraphStyleLeft = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];

    paragraphStyleLeft.lineBreakMode = NSLineBreakByWordWrapping;

    paragraphStyleLeft.alignment = NSTextAlignmentLeft;

    NSDictionary *textAttributes = @{ NSFontAttributeName: [UIFont systemFontOfSize:9.],
    NSParagraphStyleAttributeName: paragraphStyleLeft,
    NSForegroundColorAttributeName: [UIColor blackColor]};

    [self addText:@Mon super texte à  ajouter withFrame:CGRectMake(kMarginLeft+kDeltaX, kMarginTop +kDeltaY, kWidth, kHeight) withAttributes:textAttributes];
     
    // Inversion du context pour pouvoir y dessiner le pdf

    [self invertContextForPdf:context];  
    // Récupération de la première page du modèle pdf
    CFURLRef pdfURL = CFBundleCopyResourceURL(CFBundleGetMainBundle(), CFSTR("Modele.pdf"), NULL, NULL);
    CGPDFDocumentRef pdfDocumentModele = CGPDFDocumentCreateWithURL((CFURLRef)pdfURL);
    CGPDFPageRef page1 = CGPDFDocumentGetPage(pdfDocumentModele, 1);
    CGAffineTransform pdfTransform = CGPDFPageGetDrawingTransform(page1, kCGPDFCropBox, CGRectMake(0., 0., _pageSize.width, _pageSize.height), 0, true);
    CGContextConcatCTM(context, pdfTransform);


        CGContextRestoreGState(context);


     

    // Page 2


    CGRect cadreNewPage=CGRectMake(0., 0., _pageSize.width, _pageSize.height);

    UIGraphicsBeginPDFPageWithInfo(cadreNewPage, nil); [self addText:@Mon super texte à  ajouter sur la page 2 withFrame:CGRectMake(kMarginLeft+kDeltaX, kMarginTop +kDeltaY, kWidth, kHeight) withAttributes:textAttributes];   // Inversion du contexte pour pouvoir y dessiner la page 2 du pdf   [self invertContextForPdf:context]; // Récupération de la deuxième page du modèle pdf

    CGPDFPageRef page2 = CGPDFDocumentGetPage(pdfDocumentModele, 2);

    CGContextDrawPDFPage(context, page2);  


     


     

    avec la méthode d'inversion du contexte :



    -(void) invertContextForPdf:(CGContextRef)thisContext {
    CGContextTranslateCTM(thisContext, 0., _pageSize.height);
    CGContextScaleCTM(thisContext, 1.0, -1.0);
    }

    Voilà , je n'ai plus qu'à  regarder de plus près le problème des CFRelease et le contexte "restore", parce que là , c'est pas trop çà  !

    Je reviendrai corriger ce code lorsque ce sera résolu !

     

    Merci de votre aide en tout cas... 

     

    Edit : petit problème avec les balises code... j'arrive pas à  le résoudre ! désolée pour la lisibilité...


  • AliGatorAliGator Membre, Modérateur
    août 2014 modifié #9
    Je te déconseille (à  toi comme à  Eric d'ailleurs qui a l'air de faire la même chose dans son code) de réappliquer une transformation pour revenir dans le référentiel (genre appliquer une transformation X puis une transformation 1/X ensuite).
    D'autant que tu pourrais imaginer avoir des erreurs d'arrondi.

    Il y a les fonctions CGContextSaveGState() et CGContextRestoreGState() pour ça : la première "mémorise" l'état du contexte (repère, rotation, fillcolor, strokecolor, lineWidth, ...), la 2e restore l'état précédemment mémorisé.

    Donc typiquement, tu CGContextSaveGState(), puis tu inverses ton contexte, juste le temps de dessiner ton PDF, et enfin, au lieu de ré-inverser à  nouveau ton contexte, tu restores ton contexte avec CGContextRestoreGState() pour retourner dans l'état avant l'inversion.
  • Alf1996Alf1996 Membre
    août 2014 modifié #10

    Ah OK merci, je vais changer ça ; j'editerai mon message quand je serai sur mon Mac. Sur l'iPhone c'est un peu trop compliqué !


     


    Edit : çà  y est j'ai édité mon message précédent, et du coup, il y a de nouveau des problèmes avec les balises "code". Du coup, c'est de nouveau illisible...


     


    C'est moi, ou c'est un bug du forum ?




  • Je te déconseille (à  toi comme à  Eric d'ailleurs qui a l'air de faire la même chose dans son code) de réappliquer une transformation pour revenir dans le référentiel (genre appliquer une transformation X puis une transformation 1/X ensuite).

    D'autant que tu pourrais imaginer avoir des erreurs d'arrondi.


    Il y a les fonctions CGContextSaveGState() et CGContextRestoreGState() pour ça : la première "mémorise" l'état du contexte (repère, rotation, fillcolor, strokecolor, lineWidth, ...), la 2e restore l'état précédemment mémorisé.


    Donc typiquement, tu CGContextSaveGState(), puis tu inverses ton contexte, juste le temps de dessiner ton PDF, et enfin, au lieu de ré-inverser à  nouveau ton contexte, tu restores ton contexte avec CGContextRestoreGState() pour retourner dans l'état avant l'inversion.




     


    J'utilise ces deux fonctions.


    Effectivement je n'ai pas copié tout mon code mais uniquement la partie de dessin du pdf lui-même puisque j'ai aussi le dessin des poignées si l'objet est sélectionné.

Connectez-vous ou Inscrivez-vous pour répondre.