Bringing apdf from vendor into main branch.
[AROS-Contrib.git] / apdf / xpdf / Action.cc
blobb8b531a128734124cee7056c37391cc416b175eb
1 //========================================================================
2 //
3 // Action.cc
4 //
5 // Copyright 2000-2005 Emmanuel Lesueur
6 //
7 //========================================================================
9 #ifdef __GNUC__
10 #pragma implementation
11 #endif
13 #include <stddef.h>
14 #include <string.h>
15 #include "gmem.h"
16 #include "GString.h"
17 #include "Error.h"
18 #include "Object.h"
19 #include "Array.h"
20 #include "Dict.h"
21 #include "Catalog.h"
22 #include "Page.h"
23 #include "XRef.h"
24 #include "PDFDoc.h"
25 #include "Action.h"
26 #include "Annotations.h"
27 #include "AcroForm.h"
29 //------------------------------------------------------------------------
31 GString *parseFileSpecName(Object *fileSpecObj);
34 //------------------------------------------------------------------------
35 // Destination
36 //------------------------------------------------------------------------
38 Destination::Destination(Object *obj, GBool pageIsRef1) { DEBUG_INFO
39 Object obj1, obj2;
41 // initialize fields
42 pageIsRef = pageIsRef1;
43 left = bottom = right = top = zoom = 0;
44 kind = destXYZ;
45 ok = gFalse;
47 if (obj->isName()) {
49 kind = destNamed;
50 name = new GString(obj->getName());
51 ok = gTrue;
53 } else if (obj->isString()) {
55 kind = destNamed;
56 name = obj->getString()->copy();
57 ok = gTrue;
59 } else if (obj->isArray()) {
60 Array *a = obj->getArray();
62 // get page
63 if (pageIsRef) {
64 if (!a->getNF(0, &obj1)->isRef()) {
65 error(-1, "Bad annotation destination");
66 goto err2;
68 pageRef.num = obj1.getRefNum();
69 pageRef.gen = obj1.getRefGen();
70 obj1.free();
71 } else {
72 if (!a->get(0, &obj1)->isInt()) {
73 error(-1, "Bad annotation destination");
74 goto err2;
76 pageNum = obj1.getInt() + 1;
77 obj1.free();
80 // get destination type
81 a->get(1, &obj1);
83 // XYZ link
84 if (obj1.isName("XYZ")) {
85 kind = destXYZ;
86 a->get(2, &obj2);
87 if (obj2.isNull()) {
88 changeLeft = gFalse;
89 } else if (obj2.isNum()) {
90 changeLeft = gTrue;
91 left = obj2.getNum();
92 } else {
93 error(-1, "Bad annotation destination position");
94 goto err1;
96 obj2.free();
97 a->get(3, &obj2);
98 if (obj2.isNull()) {
99 changeTop = gFalse;
100 } else if (obj2.isNum()) {
101 changeTop = gTrue;
102 top = obj2.getNum();
103 } else {
104 error(-1, "Bad annotation destination position");
105 goto err1;
107 obj2.free();
108 a->get(4, &obj2);
109 if (obj2.isNull()) {
110 changeZoom = gFalse;
111 } else if (obj2.isNum()) {
112 changeZoom = gTrue;
113 zoom = obj2.getNum();
114 } else {
115 error(-1, "Bad annotation destination position");
116 goto err1;
118 obj2.free();
120 // Fit link
121 } else if (obj1.isName("Fit")) {
122 kind = destFit;
124 // FitH link
125 } else if (obj1.isName("FitH")) {
126 kind = destFitH;
127 if (!a->get(2, &obj2)->isNum()) {
128 error(-1, "Bad annotation destination position");
129 goto err1;
131 top = obj2.getNum();
132 obj2.free();
134 // FitV link
135 } else if (obj1.isName("FitV")) {
136 kind = destFitV;
137 if (!a->get(2, &obj2)->isNum()) {
138 error(-1, "Bad annotation destination position");
139 goto err1;
141 left = obj2.getNum();
142 obj2.free();
144 // FitR link
145 } else if (obj1.isName("FitR")) {
146 kind = destFitR;
147 if (!a->get(2, &obj2)->isNum()) {
148 error(-1, "Bad annotation destination position");
149 goto err1;
151 left = obj2.getNum();
152 obj2.free();
153 if (!a->get(3, &obj2)->isNum()) {
154 error(-1, "Bad annotation destination position");
155 goto err1;
157 bottom = obj2.getNum();
158 obj2.free();
159 if (!a->get(4, &obj2)->isNum()) {
160 error(-1, "Bad annotation destination position");
161 goto err1;
163 right = obj2.getNum();
164 obj2.free();
165 if (!a->get(5, &obj2)->isNum()) {
166 error(-1, "Bad annotation destination position");
167 goto err1;
169 top = obj2.getNum();
170 obj2.free();
172 // FitB link
173 } else if (obj1.isName("FitB")) {
174 kind = destFitB;
176 // FitBH link
177 } else if (obj1.isName("FitBH")) {
178 kind = destFitBH;
179 if (!a->get(2, &obj2)->isNum()) {
180 error(-1, "Bad annotation destination position");
181 goto err1;
183 top = obj2.getNum();
184 obj2.free();
186 // FitBV link
187 } else if (obj1.isName("FitBV")) {
188 kind = destFitBV;
189 if (!a->get(2, &obj2)->isNum()) {
190 error(-1, "Bad annotation destination position");
191 goto err1;
193 left = obj2.getNum();
194 obj2.free();
196 // unknown link kind
197 } else {
198 error(-1, "Unknown annotation destination type");
199 goto err2;
202 obj1.free();
203 ok = gTrue;
204 return;
206 err1:
207 obj2.free();
208 err2:
209 obj1.free();
210 } else
211 error(-1, "Bad link destination");
214 Destination::Destination(Destination *dest) { DEBUG_INFO
215 kind = dest->kind;
216 if (kind == destNamed)
217 name = dest->name->copy();
218 else {
219 pageIsRef = dest->pageIsRef;
220 if (pageIsRef)
221 pageRef = dest->pageRef;
222 else
223 pageNum = dest->pageNum;
224 left = dest->left;
225 bottom = dest->bottom;
226 right = dest->right;
227 top = dest->top;
228 zoom = dest->zoom;
229 changeLeft = dest->changeLeft;
230 changeTop = dest->changeTop;
231 changeZoom = dest->changeZoom;
233 ok = gTrue;
236 Destination::~Destination() {
237 if (kind == destNamed)
238 delete name;
242 //------------------------------------------------------------------------
243 // ActionContext
244 //------------------------------------------------------------------------
246 ActionContext::~ActionContext() {
247 if (actions)
248 gfree(actions);
251 GString *ActionContext::getBaseURI(){
252 return doc->getCatalog()->getBaseURI();
255 Annot *ActionContext::findAnnot(GString *name) {
256 AcroForm *form = getDoc()->getCatalog()->getAcroForm();
257 if (form) {
258 Field *field = form->findField(name);
259 if (field)
260 return field->getAnnot();
262 return NULL;
265 void ActionContext::push(Action *action) {
266 if (curAction = maxAction) {
267 maxAction += 8;
268 actions = (Action**)grealloc(actions, maxAction * sizeof(*actions));
270 actions[curAction++] = action;
273 Action *ActionContext::pop() {
274 if (curAction)
275 return actions[--curAction];
276 else
277 return NULL;
281 //------------------------------------------------------------------------
282 // Action
283 //------------------------------------------------------------------------
285 Action::Action(Dict *dict) { DEBUG_INFO
287 next = NULL;
289 if (dict) {
290 Object obj;
292 if (!dict->lookup("Next", &obj)->isNull())
293 next = makeAction(&obj);
294 obj.free();
298 Action::~Action() {
299 delete next;
302 void Action::add(Action *action) {
303 Action *action1 = this;
304 while (action1->next)
305 action1 = action1->next;
306 action1->next = action;
309 Action *Action::execute(ActionContext *context) {
310 Action *action2 = doExecute(context);
311 if (action2 && next)
312 context->push(next);
313 return action2 ? action2 : (next ? next : context->pop());
316 Action *Action::makeAction(Object *obj1) { DEBUG_INFO
317 Object obj2, obj3, obj4, obj5;
318 Action *action = NULL;
320 if (!obj1->isDict()) {
321 error(-1, "Invalid action");
322 return NULL;
325 obj1->dictLookup("S", &obj2);
327 // GoTo action
328 if (obj2.isName("GoTo")) {
329 action = new ActionGoTo(obj1->getDict());
331 // GoToR action
332 } else if (obj2.isName("GoToR")) {
333 action = new ActionGoToR(obj1->getDict());
335 // Launch action
336 } else if (obj2.isName("Launch")) {
337 action = new ActionLaunch(obj1->getDict());
339 // URI action
340 } else if (obj2.isName("URI")) {
341 action = new ActionURI(obj1->getDict());
343 // SetState action
344 } else if (obj2.isName("SetState")) {
345 action = new ActionSetState(obj1->getDict());
347 // Hide action
348 } else if (obj2.isName("Hide")) {
349 action = new ActionHide(obj1->getDict());
351 // Named action
352 } else if (obj2.isName("Named")) {
353 action = new ActionNamed(obj1->getDict());
355 // SubmitForm action
356 } else if (obj2.isName("SubmitForm")) {
357 action = new ActionSubmitForm(obj1->getDict());
359 // ResetForm action
360 } else if (obj2.isName("ResetForm")) {
361 action = new ActionResetForm(obj1->getDict());
363 // ImportData action
364 } else if (obj2.isName("ImportData")) {
365 action = new ActionImportData(obj1->getDict());
367 // JavaScript action
368 } else if (obj2.isName("JavaScript")) {
369 error(-1, "JavaScript not supported.");
370 action = NULL;
372 // unknown action
373 } else if (obj2.isName()) {
374 error(-1, "Unknown action type: %s.", obj2.getName());
375 action = NULL;
376 //action = new ActionUnknown(obj2.getName());
378 // action is missing or wrong type
379 } else {
380 error(-1, "Bad action");
381 action = NULL;
384 if (action && !action->isOk()) {
385 delete action;
386 action = NULL;
389 obj2.free();
390 return action;
394 //------------------------------------------------------------------------
395 // ActionGoTo
396 //------------------------------------------------------------------------
398 ActionGoTo::ActionGoTo(Dict *dict) : Action(dict) { DEBUG_INFO
399 Object obj;
401 dest = NULL;
402 if (dict->lookup("D", &obj))
403 dest = new Destination(&obj);
404 obj.free();
407 ActionGoTo::~ActionGoTo() {
408 delete dest;
411 Action *ActionGoTo::doExecute(ActionContext *context) { DEBUG_INFO
412 int num;
413 struct PDFRectangle rect;
414 FitMode mode;
415 Page *page;
416 Destination *dest1 = dest;
418 if (dest1->getKind() == destNamed) {
419 //printf("named dest: %s\n",dest1->getName()->getCString());
420 dest1 = context->getDoc()->findDest(dest1->getName());
421 if (!dest1) {
422 error(-1, "Invalid destination");
423 return NULL;
426 //printf("dest kind=%d\n",dest1->getKind());
428 if (dest1->isPageRef()) {
429 Ref ref = dest1->getPageRef();
430 num = context->getDoc()->findPage(ref.num, ref.gen);
431 } else
432 num = dest1->getPageNum();
433 page = context->getDoc()->getCatalog()->getPage(num);
435 switch (dest1->getKind()) {
436 case destXYZ:
437 mode = fitXYZ;
438 rect.x1 = dest1->getChangeLeft() ? dest1->getLeft() : -10000;
439 rect.y1 = dest1->getChangeTop() ? dest1->getTop() : -10000;
440 rect.x2 = 0;
441 rect.y2 = 0;
442 break;
443 case destFit:
444 mode = fitPage;
445 rect = *page->getMediaBox();
446 break;
447 case destFitH:
448 mode = fitH;
449 rect.y1 = dest1->getTop();
450 rect.x1 = page->getMediaBox()->x1;
451 rect.x2 = page->getMediaBox()->x2;
452 rect.y2 = 0;
453 break;
454 case destFitV:
455 mode = fitV;
456 rect.x1 = dest1->getLeft();
457 rect.y1 = page->getMediaBox()->y1;
458 rect.y2 = page->getMediaBox()->y2;
459 rect.x2 = 0;
460 break;
461 case destFitR:
462 mode = fitPage;
463 rect.x1 = dest1->getLeft();
464 rect.y1 = dest1->getBottom();
465 rect.x2 = dest1->getRight();
466 rect.y2 = dest1->getTop();
467 break;
468 case destFitB:
469 mode = fitPage;
470 rect = *page->getCropBox();
471 break;
472 case destFitBH:
473 mode = fitH;
474 rect.y1 = dest1->getTop();
475 rect.x1 = page->getCropBox()->x1;
476 rect.x2 = page->getCropBox()->x2;
477 rect.y2 = 0;
478 break;
479 case destFitBV:
480 mode = fitV;
481 rect.x1 = dest1->getLeft();
482 rect.y1 = page->getCropBox()->y1;
483 rect.y2 = page->getCropBox()->y2;
484 rect.x2 = 0;
485 break;
486 default:
487 if (dest != dest1)
488 delete dest1;
489 return NULL;
492 if (dest != dest1)
493 delete dest1;
494 context->goToPage(num, &rect, mode);
495 return NULL;
499 //------------------------------------------------------------------------
500 // ActionGoToR
501 //------------------------------------------------------------------------
503 ActionGoToR::ActionGoToR(Dict *dict) : Action(dict) { DEBUG_INFO
504 Object obj;
506 goTo = NULL;
507 fileName = NULL;
508 newWindow = gFalse;
510 if (dict->lookup("D", &obj))
511 goTo = new ActionGoTo(new Destination(&obj, gFalse));
512 obj.free();
514 if (dict->lookup("F", &obj))
515 fileName = parseFileSpecName(&obj);
516 obj.free();
518 if (dict->lookup("NewWindow", &obj)->isBool())
519 newWindow = obj.getBool();
520 obj.free();
523 ActionGoToR::~ActionGoToR() {
524 delete goTo;
525 delete fileName;
528 Action *ActionGoToR::doExecute(ActionContext *context) {
529 context->openDoc(fileName, newWindow);
530 return goTo;
534 //------------------------------------------------------------------------
535 // ActionLaunch
536 //------------------------------------------------------------------------
538 ActionLaunch::ActionLaunch(Dict *dict) : Action(dict) { DEBUG_INFO
539 Object obj;
541 file = NULL;
542 newWindow = gFalse;
544 if (dict->lookup("F", &obj))
545 file = parseFileSpecName(&obj);
546 obj.free();
548 if (dict->lookup("NewWindow", &obj)->isBool())
549 newWindow = obj.getBool();
550 obj.free();
553 ActionLaunch::~ActionLaunch() {
554 delete file;
557 Action *ActionLaunch::doExecute(ActionContext *context) {
558 context->launch(file, newWindow);
559 return NULL;
563 //------------------------------------------------------------------------
564 // ActionURI
565 //------------------------------------------------------------------------
567 ActionURI::ActionURI(Dict *dict) : Action(dict) { DEBUG_INFO
568 Object obj;
570 uri = NULL;
571 isMap = gFalse;
573 if (dict->lookup("URI", &obj)->isString())
574 uri = obj.getString()->copy();
575 obj.free();
577 if (dict->lookup("IsMap", &obj)->isBool())
578 isMap = obj.getBool();
579 obj.free();
582 ActionURI::~ActionURI() {
583 delete uri;
586 Action *ActionURI::doExecute(ActionContext *context) {
587 context->URI(uri, isMap);
588 return NULL;
592 //------------------------------------------------------------------------
593 // ActionSetState
594 //------------------------------------------------------------------------
596 ActionSetState::ActionSetState(Dict *dict) : Action(dict) { DEBUG_INFO
597 Object obj;
599 target = NULL;
600 state = NULL;
602 if (dict->lookup("T", &obj)->isString())
603 target = obj.getString()->copy();
604 obj.free();
606 if (dict->lookup("AS", &obj)->isName())
607 state = new GString(obj.getName());
608 obj.free();
611 ActionSetState::~ActionSetState() {
612 delete target;
613 delete state;
616 Action *ActionSetState::doExecute(ActionContext *context) {
617 Annot *annot;
619 if (annot = context->findAnnot(target)) {
620 annot->setAppearanceState(state->getCString());
621 context->refresh(annot);
624 return NULL;
628 //------------------------------------------------------------------------
629 // ActionHide
630 //------------------------------------------------------------------------
632 ActionHide::ActionHide(Dict *dict) : Action(dict) { DEBUG_INFO
633 Object obj;
635 target = NULL;
636 hide = gTrue;
638 if (dict->lookup("T", &obj)->isString())
639 target = obj.getString()->copy();
640 obj.free();
642 if (dict->lookup("H", &obj)->isBool())
643 hide = obj.getBool();
644 obj.free();
647 ActionHide::~ActionHide() {
648 delete target;
651 Action *ActionHide::doExecute(ActionContext *context) {
652 Annot *annot;
654 if (annot = context->findAnnot(target)) {
655 annot->hide(hide);
656 context->refresh(annot);
657 context->changeAnnotState(annot);
660 return NULL;
664 //------------------------------------------------------------------------
665 // ActionNamed
666 //------------------------------------------------------------------------
668 ActionNamed::ActionNamed(Dict *dict) : Action(dict) { DEBUG_INFO
669 dict->lookup("N", &name);
672 ActionNamed::~ActionNamed() {
673 name.free();
676 Action *ActionNamed::doExecute(ActionContext *context) {
677 context->namedAction(name.getName());
678 return NULL;
682 //------------------------------------------------------------------------
683 // ActionSubmitForm
684 //------------------------------------------------------------------------
686 ActionSubmitForm::ActionSubmitForm(Dict *dict) : Action(dict) { DEBUG_INFO
687 Object obj;
689 url = NULL;
690 flags = 0;
692 if (dict->lookup("F", &obj))
693 url = parseFileSpecName(&obj);
694 obj.free();
696 dict->lookup("Fields", &fields);
698 if (dict->lookup("Flags", &obj)->isInt())
699 flags = obj.getInt();
700 obj.free();
703 ActionSubmitForm::~ActionSubmitForm() {
704 fields.free();
705 delete url;
708 Action *ActionSubmitForm::doExecute(ActionContext *context) { DEBUG_INFO
709 AcroForm *form = context->getDoc()->getCatalog()->getAcroForm();
710 SubmitContext *submitContext;
711 int k;
713 if (fields.isArray()) {
714 Array *array = fields.getArray();
715 Object obj;
716 Field *field;
718 if (flags & submitExclude && !(flags & submitIncludeNoValue)) {
719 for (k = 0; k < form->getNumRootFields(); ++k)
720 form->getRootField(k)->setExportFlags(flags & submitIncludeNoValue);
721 } else {
722 GBool flag = (flags & submitExclude) != 0;
723 for (k = 0; k < form->getNumFields(); ++k)
724 form->getField(k)->setExportFlag(flag);
727 for (k = 0; k <array->getLength(); ++k) {
728 array->getNF(k, &obj);
729 if (obj.isRef())
730 field = form->findField(obj.getRefNum(), obj.getRefGen());
731 else if (obj.isString())
732 field = form->findField(obj.getString());
733 else {
734 field = NULL;
735 error(-1, "Invalid fields array");
737 if (field) {
738 if (flags & submitExclude) {
739 field->setExportFlag(gFalse);
741 } else {
742 field->setExportFlags(flags & submitIncludeNoValue);
745 obj.free();
748 } else {
749 for (k = 0; k < form->getNumRootFields(); ++k)
750 form->getRootField(k)->setExportFlags((flags & submitIncludeNoValue) != 0);
753 if (flags & submitExportHTML) {
754 if (flags & submitGetMethod) {
755 submitContext = new SubmitHTMLGetContext;
756 } else {
757 submitContext = new SubmitHTMLPostContext("t:tmp.html"); //~
759 } else {
760 submitContext = new SubmitFDFContext("t:tmp.fdf"); //~
763 for (k = 0; k < form->getNumRootFields(); ++k)
764 form->getRootField(k)->submit(submitContext);
766 if (flags & submitExportHTML) {
767 //~ map
768 if (flags & submitGetMethod) {
769 GString *str = url->copy();
770 str->append('?');
771 str->append(((SubmitHTMLGetContext *)submitContext)->getContents());
772 context->URI(str, gFalse);
773 delete str;
774 } else {
775 context->post(url, "t:tmp.html");
777 } else {
778 context->post(url, "t:tmp.fdf");
781 delete submitContext;
783 return NULL;
787 //------------------------------------------------------------------------
788 // ActionResetForm
789 //------------------------------------------------------------------------
791 ActionResetForm::ActionResetForm(Dict *dict) : Action(dict) { DEBUG_INFO
792 Object obj;
794 flags = 0;
796 dict->lookup("Fields", &fields);
798 if (dict->lookup("Flags", &obj)->isInt())
799 flags = obj.getInt();
800 obj.free();
803 ActionResetForm::~ActionResetForm() {
804 fields.free();
807 Action *ActionResetForm::doExecute(ActionContext *context) { DEBUG_INFO
808 AcroForm *form = context->getDoc()->getCatalog()->getAcroForm();
810 if (fields.isArray()) {
811 Array *array = fields.getArray();
812 int k, l;
813 Object obj;
814 Field *field;
815 Field **fields;
817 if (flags & 1) {
818 fields = new Field * [form->getNumFields()];
819 for (k = 0; k < form->getNumFields(); ++k)
820 fields[k] = form->getField(k);
823 for (k = 0; k <array->getLength(); ++k) {
824 array->getNF(k, &obj);
825 if (obj.isRef())
826 field = form->findField(obj.getRefNum(), obj.getRefGen());
827 else if (obj.isString())
828 field = form->findField(obj.getString());
829 else {
830 field = NULL;
831 error(-1, "Invalid fields array");
833 if (field) {
834 if (flags & 1) {
835 // reset all fields except those in the array.
836 for (l = 0; l < form->getNumFields(); ++l)
837 if (fields[l] == field) {
838 fields[l] = NULL;
839 break;
841 } else {
842 // reset fields designated by the array, including kids.
843 field->resetFieldsTree();
846 obj.free();
849 if (flags & 1) {
850 for (l = 0; l < form->getNumFields(); ++l)
851 if (fields[l])
852 fields[l]->reset();
853 delete [] fields;
856 } else {
857 int k;
859 for (k = 0; k < form->getNumFields(); ++k) {
860 form->getField(k)->reset();
863 context->refresh();
864 return NULL;
868 //------------------------------------------------------------------------
869 // ActionImportData
870 //------------------------------------------------------------------------
872 ActionImportData::ActionImportData(Dict *dict) : Action(dict) { DEBUG_INFO
873 Object obj;
875 file = NULL;
877 if (dict->lookup("F", &obj))
878 file = parseFileSpecName(&obj);
879 obj.free();
882 ActionImportData::~ActionImportData() {
883 delete file;
886 Action *ActionImportData::doExecute(ActionContext *context) { DEBUG_INFO
887 AcroForm *form = context->getDoc()->getCatalog()->getAcroForm();
889 if (form && form->importFDF(file->copy())) {
890 context->refresh();
893 return NULL;
897 //------------------------------------------------------------------------
898 // AdditionalAction
899 //------------------------------------------------------------------------
901 AdditionalAction::AdditionalAction(Dict *dict) { DEBUG_INFO
902 int k;
904 for (k = 0; k < maxTrig; ++k)
905 actions[k] = NULL;
906 if (dict) {
907 for (k = 0; k < maxTrig; ++k) {
908 Object obj;
909 char key[2];
911 key[0] = "EXDUOCFVK" [k];
912 key[1] = '\0';
914 if (dict->lookup(key, &obj)->isDict())
915 actions[k] = Action::makeAction(&obj);
916 obj.free();
921 AdditionalAction::~AdditionalAction() {
922 int k;
923 for (k = 0; k < maxTrig; ++k)
924 delete actions[k];
927 void AdditionalAction::setAction(ActionTrigger t, Action *action) {
928 action->add(actions[t]);
929 actions[t] = action;
933 //------------------------------------------------------------------------
935 // Extract a file name from a file specification (string or dictionary).
936 GString *parseFileSpecName(Object *fileSpecObj) { DEBUG_INFO
937 GString *name;
938 Object obj1;
940 name = NULL;
942 // string
943 if (fileSpecObj->isString()) {
944 name = fileSpecObj->getString()->copy();
946 // dictionary
947 } else if (fileSpecObj->isDict()) {
948 if (!fileSpecObj->dictLookup("Unix", &obj1)->isString()) {
949 obj1.free();
950 fileSpecObj->dictLookup("F", &obj1);
952 if (obj1.isString())
953 name = obj1.getString()->copy();
954 else
955 error(-1, "Illegal file spec in link");
956 obj1.free();
958 // error
959 } else {
960 error(-1, "Illegal file spec in link");
963 return name;