Technical Article

Reading PDF Forms: MCID Namespaces and Reused Content

A PDF page and two nested Forms can each contain MCID 0, so the number alone cannot identify the text a reader should select. PDFium Component follows tagged Form stream and structure relationships, and tracks individual occurrences when reading reused untagged Forms

A Form XObject can supply text that belongs in a document's logical reading order, even though the drawing instructions live in a different stream from the page. PDFium Component uses the stream and its structure relationship together with the MCID when reading tagged Form content. The resulting reading order can follow the author's structure, while selection and speech retain a route back to the particular occurrence painted on the page

What does an MCID identify?

An /MCID identifies marked content in its own stream. A page stream can use /MCID 0; an invoked Form can use 0 too; a nested Form can use another 0. The integers match; their owners do not. A reader that builds one document-wide table keyed only by the integer can attach a diagram label to the wrong paragraph or send a highlight to the wrong occurrence. The structure tree associates marked content with elements such as headings and paragraphs, while the page invocation locates the Form's drawing. We would begin a reading-order investigation with both the stream and the structural reference. Paint order alone is a weaker clue: a PDF producer can draw a diagram before a heading that the logical structure places ahead of it

How does the ParentTree identify the owner?

A marked-content reference, commonly called an MCR, can identify its stream through /Stm. When /Stm points to a Form, the MCID is interpreted in that Form's namespace; a page MCID or an MCR without /Stm uses the page namespace. /Pg connects the reference to its page. The stream or page carries a /StructParents key, which the document's /ParentTree maps to an array whose MCID slot identifies the structure element. Check the relationship in both directions when you trace an MCID: the structure item points to content, and the ParentTree entry points back to the corresponding element. ISO 32000-1 sections 14.7.4.3 and 14.7.4.4 describe these relationships. Record the stream and page with an MCID when diagnosing the document; otherwise several legitimate uses of zero are indistinguishable in a log

Turn on tagged Form reading

Tagged Form reading is explicitly enabled and requires the base Form option. Configure the existing TPdf after loading its document and before requesting readable content

uses
  PDFium;

procedure EnableTaggedForms(Pdf: TPdf);
var
  Options: TPdfReadableContentOptions;
begin
  Options := TPdfReadableContentOptions.Default;
  Options.IncludeFormXObjects := True;
  Options.IncludeTaggedFormXObjects := True;
  Pdf.ReadableContentOptions := Options;
end;

For tagged content, the reader follows the structure tree's /K order, which can differ from the order in which graphics are painted. A heading drawn after a decorative background may still come first in the intended reading sequence. After enabling the option, print the fragment order along with its marked-content identifiers and object-path length. A number by itself cannot tell us whether an entry came from the page stream or from a Form stream, so the path information gives the host application more context for the returned occurrence

procedure DumpTaggedFragments(Pdf: TPdf);
var
  Content: TPdfReadableContent;
  I, J: Integer;
begin
  Content := Pdf.ReadableContent;
  for I := 0 to High(Content.Fragments) do
  begin
    Writeln(Content.Fragments[I].Text);
    Writeln('Object path length: ',
      Length(Content.Fragments[I].NativeObjectPath));
    for J := 0 to High(Content.Fragments[I].MarkedContentIDs) do
      Writeln('  MCID: ', Content.Fragments[I].MarkedContentIDs[J]);
  end;
end;

In the page, outer Form and inner Form example, several returned entries can legitimately use the same MCID. Follow their page and Form structure relationships before you assign an owner. Retain the returned fragments or reading units for selection and speech: the component keeps the path back to each text occurrence and its page-space geometry. Editing or reloading the document changes the underlying document state, so rebuild the reading units afterward rather than applying an old selection to a new set of page objects

Reuse the number in different streams

Build a page with one text item in the page stream, one in an outer Form and one in a nested Form. Give all three MCID 0, with a distinct structure element and ParentTree entry for each. Labels such as PAGE, OUTER and INNER make the expected order easy to recognize. Enable tagged Form reading, read the returned sequence and select each fragment to check its highlight. Move the outer Form in another document and verify that its highlight follows the new page location. An intentionally inconsistent ParentTree reference can be kept in a clearly labeled invalid copy for diagnostic testing. Include multiple text objects in one marked-content item and a crop offset or rotated Form: structure identifies the text's owner, while the transformations determine its position on the page

Logical order and visible content

When you receive a report saying “MCID 0” was read in the wrong place, the number needs its context. Find the enclosing page or Form, the Form's /StructParents key and the structure element that refers to the marked item. Several zeros on one page can be valid. Decorative graphics may appear in the painted output without belonging to the reading flow, while visible text can carry a particular structural role or language. A smaller document can help isolate the relationships, but moving a page or Form can change resource names and object numbers. Verify the structure references in your reduced document before drawing conclusions from its reading sequence

Why can the same Form produce two reading sources?

Reuse introduces a related question for untagged Forms. A page can invoke the same stamp twice with Do; both occurrences can contain identical text, share the same resource and even occupy identical rectangles. Matching only text and geometry cannot tell the host which invocation was selected. PDFium Component offers an option for shared untagged Form occurrences, tying each invocation to its own object path so selection and speech can return to that occurrence. Enable it with the base Form option when the application reads repeated untagged content

procedure EnableSharedForms(Pdf: TPdf);
var
  Options: TPdfReadableContentOptions;
begin
  Options := TPdfReadableContentOptions.Default;
  Options.IncludeFormXObjects := True;
  Options.IncludeSharedFormXObjects := True;
  Pdf.ReadableContentOptions := Options;
end;

Use the returned fragments with the component's selection and speech helpers instead of searching again for the same text. After you edit or reload, obtain a fresh reading snapshot: the object path belongs to the current document state and should be interpreted in that state

Inspect repeated occurrences before selecting

The following diagnostic prints the extracted entries and the shared-Form expansion status. Two overlapping stamps can look like one mark even when the page invokes the Form twice, so check the returned occurrences as well as the visible result. Identical text in those entries can reflect two invocations of the same resource, and a selection must retain the occurrence path needed to return to the intended one. The expansion status and returned object paths give the host application information about the occurrences it is reading. Selection should use those returned entries, especially when two invocations paint the same text in the same place and a text search would find more than one possible match

procedure InspectSharedForms(Pdf: TPdf);
var
  Content: TPdfReadableContent;
  I: Integer;
begin
  Content := Pdf.ReadableContent;
  Writeln('Expanded Forms: ', Content.NestedFormsExpanded);
  Writeln('Complete: ', Content.NestedFormsComplete);
  if not Content.NestedFormsComplete then
    Writeln(Content.NestedFormsError);
  for I := 0 to High(Content.Fragments) do
    Writeln(I, ': ', Content.Fragments[I].Text,
      ' (path length ', Length(Content.Fragments[I].NativeObjectPath), ')');
end;

Equal path lengths do not establish that two entries refer to the same invocation. Exercise selection for each entry you receive, reload the document and build a new reading snapshot. Tagged logical order and repeated untagged content use their respective reader options because they address different relationships in the PDF. The ParentTree guide covers structural relationships during editing, and the accessible reader workflow covers navigation and highlighting. These options are part of PDFium Component for Delphi