Skip to content

Commit 6157acf

Browse files
authored
Use CID font (Identity-H encoding) for full Unicode support (#181)
Full Unicode support for PDF text rendering by switching from simple font encoding to CID fonts with Identity-H encoding. The change enables proper rendering of international characters including Latin accents, Greek, Cyrillic, and special characters that were previously unsupported Key changes: - Replaced simple font encoding (Latin/Greek/Cyrillic) with CID font encoding for universal Unicode support - Added test coverage for various Unicode character sets and special characters - Introduced three new PDF validation functions to check - Password requirements - Edit restrictions - And DocMDP P1 certification status
1 parent 891e0ad commit 6157acf

4 files changed

Lines changed: 474 additions & 35 deletions

File tree

pdf_handler.c

Lines changed: 182 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)