Counts the number of elements on the operand stack from the top down to (but not including) the topmost mark object.

1. Description

The counttomark operator counts how many elements are on the operand stack between the top and the nearest mark object (searching from top to bottom). The mark itself is not counted, and the stack is left unchanged except for the count value being pushed.

This operator is essential for implementing variable-argument procedures where the number of arguments is not known in advance. It’s commonly used with mark to process all elements in a marked segment.

This is a Level 1 operator, available in all PostScript implementations.

2. Syntax

mark obj1 ... objn counttomark mark obj1 ... objn n

2.1. Stack Effect

Table 1. Before Execution
Position Content

Top

objn - Topmost object above mark

…​

obj2, obj1 - Intermediate objects

Bottom (of segment)

mark - Mark object (not included in count)

Table 2. After Execution
Position Content

Top

n (integer) - Number of objects between top and mark

Top-1

objn - Original top object

…​

obj2, obj1 - Original intermediate objects

Bottom (of segment)

mark - Mark object (unchanged)

3. Parameters

Requires at least one mark object on the stack. The operator counts all objects above the topmost mark.

obj1 …​ objn

Any objects (of any type other than marks) between the top of the stack and the nearest mark.

mark

A mark object (type marktype) that delimits the counting boundary.

4. Return Values

Returns a non-negative integer representing the number of objects between the top of the stack and the nearest mark (exclusive of the mark itself).

5. Examples

5.1. Basic Usage

% Count elements after mark
mark 1 2 3 counttomark
% Stack: mark 1 2 3 3

% Empty marked segment
mark counttomark
% Stack: mark 0

% Multiple elements
1 mark 2 3 counttomark
% Stack: 1 mark 2 3 2

5.2. Variable Argument Functions

% Sum all arguments after mark
/sumAll {           % mark n1 n2 ... nk -> sum
    0               % Initialize sum
    counttomark {   % Loop count times
        exch add
    } repeat
    exch pop        % Remove mark
} def

% Usage
mark 1 2 3 4 5 sumAll   % Stack: 15
mark 10 20 sumAll       % Stack: 30

5.3. Processing Variable Arguments

% Multiply all arguments
/multiplyAll {      % mark n1 n2 ... nk -> product
    1               % Initialize product
    counttomark {
        exch mul
    } repeat
    exch pop        % Remove mark
} def

mark 2 3 4 multiplyAll  % Stack: 24

5.4. Array Construction from Mark

% Create array from marked elements
/arrayFromMark {    % mark elem1 ... elemn -> array
    counttomark     % Count elements
    array astore    % Create and fill array
    exch pop        % Remove mark
} def

mark 1 2 3 4 5 arrayFromMark
% Stack: [1 2 3 4 5]

6. Advanced Examples

6.1. Collecting Elements Conditionally

% Collect only positive numbers
/collectPositive {  % mark n1 n2 ... nk -> array
    counttomark     % Get count
    /temp exch array def
    0               % Index counter
    {
        counttomark 0 eq { exit } if
        dup 0 gt {
            temp 3 1 roll put
            1 add
        } {
            pop
        } ifelse
    } loop
    temp 0 3 -1 roll getinterval
    exch pop        % Remove mark
} def

mark -5 3 -2 7 1 -8 4 collectPositive
% Stack: [3 7 1 4]

6.2. Building Nested Structures

% Create nested array from multiple marks
/nestedArray {      % mark1 ... markn elem... -> nested_array
    counttomark array astore
    {
        % Check if contains mark
        dup 0 get type /marktype eq {
            % Process nested level
            1 1 index length 1 sub getinterval
            nestedArray
        } if
    } forall
} def

6.3. Safe Argument Processing

% Process exactly n arguments after mark
/processN {         % mark obj1 ... objk n proc -> result
    exch            % mark obj1 ... objk proc n
    counttomark     % mark obj1 ... objk proc n count
    1 index ne {
        pop pop
        (Error: argument count mismatch) print
        cleartomark
    } {
        pop         % Remove n
        counttomark {
            2 copy exec
        } repeat
        pop         % Remove proc
        exch pop    % Remove mark
    } ifelse
} def

6.4. Variable-Length Dictionary Builder

[source,postscript>

% Build dictionary from key-value pairs
/makeDict {         % mark /key1 val1 ... /keyn valn -> dict
    counttomark 2 idiv  % Count key-value pairs
    dup dict begin
        {
            def
        } repeat
    currentdict end
    exch pop        % Remove mark
} def

mark
/name (PostScript)
/level 3
/year 1999
makeDict

7. Edge Cases and Common Pitfalls

If no mark exists on the stack, counttomark causes an unmatchedmark error.

7.1. No Mark on Stack

% BAD: No mark to count to
clear
1 2 3
counttomark         % ERROR: unmatchedmark

% GOOD: Always ensure mark exists
mark 1 2 3
counttomark         % OK, returns 3

7.2. Only Counts to First Mark

% CAUTION: Only counts to nearest mark
mark 1 2 mark 3 4 5
counttomark
% Stack: mark 1 2 mark 3 4 5 3
% Only counted 3 4 5 (after second mark)

cleartomark         % Remove inner segment
counttomark
% Stack: mark 1 2 2
% Now counts 1 2 (after first mark)

7.3. Count Adds to Stack

% Remember: counttomark adds count to stack
mark 1 2 3
counttomark         % Stack: mark 1 2 3 3
count               % Stack: mark 1 2 3 3 5
% Stack now has 5 elements (including mark and count)
Use counttomark immediately before the operation that needs the count. Don’t store the count for later use if the stack might change.

7.4. Empty Marked Segment

% Valid case: mark with no elements
mark
counttomark         % Stack: mark 0
% Returns 0 for empty segment
  • mark - Push a mark object onto stack

  • cleartomark - Remove elements down to and including mark

  • count - Count total stack depth

  • ] - Create array from marked elements

  • pop - Remove single element

9. PostScript Level

Available in: PostScript Level 1 and higher

This is a fundamental operator available in all PostScript implementations.

10. Error Conditions

unmatchedmark

No mark object is found on the operand stack when searching from top to bottom.

clear
1 2 3
counttomark         % ERROR: unmatchedmark
stackoverflow

The stack is at maximum capacity and cannot accommodate the count value. This is extremely rare in practice.

% (Only possible if stack nearly full)

11. Performance Considerations

The counttomark operator has O(n) time complexity where n is the number of elements between the top of the stack and the mark. The operator must scan the stack to find the mark and count elements.

For very deep marks, this can be slightly expensive, but in practice marked segments are usually small and performance is not a concern.

12. Best Practices

  1. Use with mark: Always ensure a mark exists before calling counttomark

  2. Immediate use: Use the count value immediately; don’t store it for later

  3. Variable arguments: Ideal for implementing procedures with variable-length argument lists

  4. Document expectations: Clearly document when procedures expect marked arguments

  5. Verify count: For robust code, verify the count matches expectations before processing

12.1. Variable Argument Pattern

% Standard pattern for variable arguments
/varArgProc {       % mark arg1 ... argn -> result
    % Get count
    counttomark

    % Process that many arguments
    {
        % Process each argument
        % ... operation ...
    } repeat

    % Clean up mark
    exch pop
} def

12.2. Defensive Counting

% Verify argument count
/strictArgProc {    % mark arg1 ... argn expected -> result
    counttomark
    2 copy ne {
        (Error: expected ) print dup =
        (got ) print =
        cleartomark
    } {
        pop         % Remove expected
        % Process arguments
        % ...
        exch pop    % Remove mark
    } ifelse
} def

12.3. Safe Mark Handling

% Check for mark before counting
/safeCountToMark {  % ... -> ... n (or 0 if no mark)
    false           % Found flag
    count 1 sub 0 1 3 -1 roll {
        index type /marktype eq {
            % Found mark, count elements
            pop
            0 exch 0 1 3 -1 roll {
                pop 1 add
            } for
            true exit
        } if
    } for

    not {
        0           % No mark found, return 0
    } if
} def

13. See Also


Back to top

Copyright © 2025 Ribose. PostScript is a trademark of Adobe. Distributed under the MIT License.