@@ -60,7 +60,7 @@ int page_insert_content_to_content_stream(fz_context *ctx, pdf_page *page, fz_bu
6060 if (append )
6161 pdf_array_push (ctx , existing_content , new_stream );
6262 else
63- pdf_array_insert (ctx , existing_content , new_stream , 0 );
63+ pdf_array_insert (ctx , existing_content , new_stream , 0 );
6464 }
6565 else {
6666 pdf_obj * array = pdf_new_array (ctx , page -> doc , 5 );
@@ -90,7 +90,7 @@ void wrap_page_contents(fz_context *ctx, pdf_page *page) {
9090
9191 int prepend = 0 ;
9292 int append = 0 ;
93-
93+
9494 resources = pdf_dict_get (ctx , page -> obj , PDF_NAME (Resources ));
9595 contents = pdf_dict_get (ctx , page -> obj , PDF_NAME (Contents ));
9696
@@ -352,13 +352,33 @@ addImageOutput add_image_to_page(pdfDocument document, addImageInput input) {
352352 return output ;
353353}
354354
355- void page_add_text (fz_context * ctx , pdf_page * page , const char * text , fz_point position , fz_font * font , float font_size , const char * encoding_name ) {
355+ // Convert UTF-8 text to CID hex string for Identity-H encoded CID fonts
356+ // For CID fonts, each character is encoded as a 2-byte CID (glyph ID)
357+ void append_text_as_cid_hex_string (fz_context * ctx , fz_buffer * buf , fz_font * font , const char * text ) {
358+ int c , gid ;
359+ const char * s = text ;
360+
361+ fz_append_byte (ctx , buf , '<' ); // Start hex string
362+
363+ while (* s ) {
364+ s += fz_chartorune (& c , s ); // Decode UTF-8 to Unicode codepoint
365+ gid = fz_encode_character (ctx , font , c ); // Get glyph ID for this character
366+ if (gid <= 0 )
367+ gid = 0 ; // Use .notdef glyph if character not found
368+
369+ // Write glyph ID as 4-digit hex (2 bytes)
370+ fz_append_printf (ctx , buf , "%04x" , gid );
371+ }
372+
373+ fz_append_byte (ctx , buf , '>' ); // End hex string
374+ }
375+
376+ void page_add_text (fz_context * ctx , pdf_page * page , const char * text , fz_point position , fz_font * font , float font_size ) {
356377 fz_buffer * stream = NULL ;
357378 pdf_obj * resources = NULL ;
358379 pdf_obj * font_dict = NULL ;
359380 pdf_obj * font_ref = NULL ;
360381 char resource_name [32 ] = "" ;
361- int encoding = PDF_SIMPLE_ENCODING_LATIN ;
362382 fz_matrix matrix = fz_identity ;
363383 size_t max_length = 300 ;
364384
@@ -372,24 +392,14 @@ void page_add_text(fz_context *ctx, pdf_page *page, const char *text, fz_point p
372392 fz_throw (ctx , FZ_ERROR_GENERIC , "Text exceeds maximum allowed size. Expected: %zu, Actual: %zu" , max_length , text_length );
373393 }
374394
375- stream = fz_new_buffer (ctx , text_length + 500 );
395+ stream = fz_new_buffer (ctx , text_length * 2 + 500 );
376396
377- // Add font to Resources
397+ // Add font to Resources using CID font for full Unicode support
378398 resources = get_or_create_dict (ctx , page -> obj , PDF_NAME (Resources ));
379399 font_dict = get_or_create_dict (ctx , resources , PDF_NAME (Font ));
380400
381- if (encoding_name ) {
382- if (!strcmp (encoding_name , "Latin" )) {
383- encoding = PDF_SIMPLE_ENCODING_LATIN ;
384- }
385- else if (!strcmp (encoding_name , "Greek" )) {
386- encoding = PDF_SIMPLE_ENCODING_GREEK ;
387- }
388- else if (!strcmp (encoding_name , "Cyrillic" )) {
389- encoding = PDF_SIMPLE_ENCODING_CYRILLIC ;
390- }
391- }
392- font_ref = pdf_add_simple_font (ctx , page -> doc , font , encoding );
401+ // Use CID font (Identity-H encoding) for full Unicode support
402+ font_ref = pdf_add_cid_font (ctx , page -> doc , font );
393403 fz_snprintf (resource_name , sizeof (resource_name ), "Font%d" , pdf_to_num (ctx , font_ref ));
394404 pdf_dict_puts (ctx , font_dict , resource_name , font_ref );
395405
@@ -410,9 +420,8 @@ void page_add_text(fz_context *ctx, pdf_page *page, const char *text, fz_point p
410420 font_size
411421 ); // Sets the font
412422 fz_append_string (ctx , stream , "0 0 Td\n" ); // Set the text position
413- fz_append_printf (ctx , stream , "(%s) Tj\n" ,
414- text
415- ); // Draw text
423+ append_text_as_cid_hex_string (ctx , stream , font , text ); // Append text as hex string for CID font
424+ fz_append_string (ctx , stream , " Tj\n" ); // Draw text operator
416425 fz_append_string (ctx , stream , "ET\n" ); // Ends the text object.
417426 fz_append_string (ctx , stream , "Q\n" ); // Restores the previously saved graphics state
418427 page_add_content_to_content_stream (ctx , page , stream );
@@ -458,7 +467,7 @@ addTextOutput add_text_to_page(pdfDocument document, addTextInput input) {
458467 }
459468 fz_point position = {input .x , input .y };
460469
461- page_add_text (ctx , page , input .text , position ,font , input .font_size , "Latin" );
470+ page_add_text (ctx , page , input .text , position , font , input .font_size );
462471 }
463472 fz_always (ctx ) {
464473 fz_drop_font (ctx , font );
@@ -645,10 +654,10 @@ saveToPNGOutput save_to_png_file(pdfDocument document, saveToPNGInput input) {
645654 params .scale = input .scale ;
646655 params .dpi = input .dpi ;
647656 params .cookie = input .cookie ;
648-
657+
649658 // Call the main.c function
650659 save_to_png_output main_output = save_to_png_with_document (ctx , doc , params );
651-
660+
652661 // Convert save_to_png_output to saveToPNGOutput
653662 output .payload = main_output .payload ;
654663 output .payload_length = main_output .payload_length ;
@@ -658,7 +667,7 @@ saveToPNGOutput save_to_png_file(pdfDocument document, saveToPNGInput input) {
658667 } fz_catch (ctx ) {
659668 output .error = strdup (fz_caught_message (ctx ));
660669 }
661-
670+
662671 fz_drop_context (ctx );
663672 return output ;
664673}
@@ -667,7 +676,7 @@ wrapPageOutput wrap_page_contents_for_page(pdfDocument document, int page_number
667676 wrapPageOutput output = { .error = NULL };
668677 pdf_document * pdf = NULL ;
669678 pdf_page * page = NULL ;
670-
679+
671680 fz_context * ctx = fz_clone_context (global_ctx );
672681 if (!ctx ) {
673682 output .error = strdup ("Context clone failed" );
@@ -691,3 +700,150 @@ wrapPageOutput wrap_page_contents_for_page(pdfDocument document, int page_number
691700 return output ;
692701}
693702
703+
704+ checkRestrictionsOutput check_pdf_restrictions (pdfDocument input ) {
705+ checkRestrictionsOutput output = { .is_restricted = 0 , .error = NULL };
706+
707+ fz_context * ctx = fz_clone_context (global_ctx );
708+ if (!ctx ) {
709+ output .error = strdup ("Context clone failed" );
710+ return output ;
711+ }
712+
713+ fz_try (ctx ) {
714+ pdf_document * pdf_doc = (pdf_document * )input .handle ;
715+ fz_document * doc = (fz_document * )pdf_doc ;
716+
717+ int has_edit_permission = fz_has_permission (ctx , doc , FZ_PERMISSION_EDIT );
718+ int has_annotate_permission = fz_has_permission (ctx , doc , FZ_PERMISSION_ANNOTATE );
719+
720+ if (!has_edit_permission || !has_annotate_permission ) {
721+ output .is_restricted = 1 ;
722+ }
723+
724+ } fz_catch (ctx ) {
725+ output .error = strdup (fz_caught_message (ctx ));
726+ }
727+
728+ fz_drop_context (ctx );
729+ return output ;
730+ }
731+ checkPasswordOutput check_pdf_password (pdfDocument input ) {
732+ checkPasswordOutput output = { .needs_password = 0 , .error = NULL };
733+
734+ fz_context * ctx = fz_clone_context (global_ctx );
735+ if (!ctx ) {
736+ output .error = strdup ("Context clone failed" );
737+ return output ;
738+ }
739+
740+ fz_try (ctx ) {
741+ pdf_document * pdf_doc = (pdf_document * )input .handle ;
742+ fz_document * doc = (fz_document * )pdf_doc ;
743+
744+ // Check if the document is encrypted with a non-blank password
745+ // Returns non-zero if a password is required to access the document
746+ int needs_password = fz_needs_password (ctx , doc );
747+
748+ if (needs_password ) {
749+ output .needs_password = 1 ;
750+ }
751+
752+ } fz_catch (ctx ) {
753+ output .error = strdup (fz_caught_message (ctx ));
754+ }
755+
756+ fz_drop_context (ctx );
757+ return output ;
758+ }
759+
760+ static int check_docmdp_p_value (fz_context * ctx , pdf_obj * transform_params )
761+ {
762+ if (!transform_params )
763+ return 0 ;
764+
765+ transform_params = pdf_resolve_indirect (ctx , transform_params );
766+ // Get the P (Permission) value from TransformParams
767+ // P=1: No changes allowed (most restrictive - DocMDP P1)
768+ // P=2: Form filling and signing allowed
769+ // P=3: Form filling, signing, and annotations allowed
770+ int p = pdf_dict_get_int (ctx , transform_params , PDF_NAME (P ));
771+ return p == 1 ;
772+ }
773+
774+ static int check_sig_value_for_docmdp_p1 (fz_context * ctx , pdf_obj * sig )
775+ {
776+ if (!sig )
777+ return 0 ;
778+
779+ sig = pdf_resolve_indirect (ctx , sig );
780+
781+ pdf_obj * ref = pdf_dict_get (ctx , sig , PDF_NAME (Reference ));
782+ if (!ref || !pdf_is_array (ctx , ref ))
783+ return 0 ;
784+
785+ int n = pdf_array_len (ctx , ref );
786+ for (int i = 0 ; i < n ; ++ i )
787+ {
788+ pdf_obj * sig_ref = pdf_resolve_indirect (ctx , pdf_array_get (ctx , ref , i ));
789+ if (!sig_ref )
790+ continue ;
791+
792+ pdf_obj * method = pdf_dict_get (ctx , sig_ref , PDF_NAME (TransformMethod ));
793+ if (!pdf_name_eq (ctx , method , PDF_NAME (DocMDP )))
794+ continue ;
795+
796+ pdf_obj * params = pdf_dict_get (ctx , sig_ref , PDF_NAME (TransformParams ));
797+ if (check_docmdp_p_value (ctx , params ))
798+ return 1 ;
799+ }
800+
801+ // Nonstandard fallback: some broken files may put TransformParams directly on /Sig
802+ pdf_obj * direct_params = pdf_dict_get (ctx , sig , PDF_NAME (TransformParams ));
803+ if (check_docmdp_p_value (ctx , direct_params ))
804+ return 1 ;
805+ return 0 ;
806+ }
807+
808+ checkDocMdpOutput check_pdf_docmdp_p1 (pdfDocument input )
809+ {
810+ checkDocMdpOutput output = {0 , NULL };
811+ fz_context * ctx = fz_clone_context (global_ctx );
812+ if (!ctx )
813+ {
814+ output .error = strdup ("Context clone failed" );
815+ return output ;
816+ }
817+
818+ fz_try (ctx )
819+ {
820+ pdf_document * doc = (pdf_document * )input .handle ;
821+
822+ pdf_obj * catalog = pdf_dict_get (ctx , pdf_trailer (ctx , doc ), PDF_NAME (Root ));
823+ pdf_obj * perms = pdf_dict_get (ctx , catalog , PDF_NAME (Perms ));
824+ pdf_obj * docmdp = perms ? pdf_dict_get (ctx , perms , PDF_NAME (DocMDP )) : NULL ;
825+
826+ if (docmdp )
827+ {
828+ docmdp = pdf_resolve_indirect (ctx , docmdp );
829+
830+ pdf_obj * type = pdf_dict_get (ctx , docmdp , PDF_NAME (Type ));
831+ pdf_obj * sig = NULL ;
832+
833+ if (pdf_name_eq (ctx , type , PDF_NAME (Sig )))
834+ sig = docmdp ; // direct signature
835+ else
836+ sig = pdf_dict_get (ctx , docmdp , PDF_NAME (V )); // field V
837+
838+ if (check_sig_value_for_docmdp_p1 (ctx , sig ))
839+ output .has_docmdp_p1 = 1 ;
840+ }
841+ }
842+ fz_catch (ctx )
843+ {
844+ output .error = strdup (fz_caught_message (ctx ));
845+ }
846+
847+ fz_drop_context (ctx );
848+ return output ;
849+ }
0 commit comments