Skip to content

Releases: KenM76/pdfcer

v0.52.0 — find and remove content drawn outside the page

Choose a tag to compare

@KenM76 KenM76 released this 11 Sep 04:07

Moving something off the sheet is not deleting it

A page box (/CropBox, or /MediaBox when there is none) is what a reader
displays. A content stream may draw anywhere. Marks outside the box are
still in the file: they print on a larger sheet, they survive a page-box
change, they reappear the moment somebody crops differently, and text among
them is still extractable and searchable.

Two new commands find that content and take it out.

Find it

pdfcer scan-offpage <files|folders…> [--recursive] [--tolerance PT]
                    [--detail | --files-only] [-o report.txt]

Files, folders, or a whole tree. Every file and every page is scanned
finding something does not stop the walk, and neither does a file that will
not open (that one is reported and the scan continues).

The exit code is 0 when nothing was found and 1 when something was, so
a script can branch on it. That is a verdict on the whole run, delivered at the
end.

For one log of everything in a single pass:

pdfcer scan-offpage R:/Products --recursive --detail -o report.txt

--files-only instead prints one path per affected file, which is the form
that pipes into a copy or a batch.

Measured on 341 real engineering drawings: 176 of them draw outside the
sheet
— 554 pages, 471,840 objects entirely off-canvas, 1,152 crossing the
edge. One sheet carried 15,927 off-page objects: an entire second drawing
parked to the left of the page.

Remove it, keeping what is on the page

pdfcer redact-offpage <files|folders…> [--recursive]
                      (-o FILE | --out-dir DIR) [--suffix -offpage]
                      [--force] [--dry-run]

Objects wholly off the page are dropped. Objects crossing the edge are cut
at the edge
, so the part on the sheet is kept — a line that runs off the
page keeps the segment that was on it, a text run keeps the glyphs that were
visible, an image keeps the samples inside the box.

That is redaction machinery, not a clip: the off-page bytes are removed from
the content stream rather than hidden, because a clipped path is a path whose
data survives. The same code cuts at the page edge that cuts at the edge of an
operator's redaction rectangle — one behaviour, not two.

Batch output mirrors the input tree under --out-dir rather than
flattening it: two product folders can hold drawings with the same file name,
and a flat folder would silently make one of them the other. An existing
output is skipped and counted unless --force, so an interrupted batch
resumes.

⚠️ Destructive and deliberate. Keep the input; --dry-run first.

The tolerance, and what it does not do

A border stroked exactly on the page boundary overhangs by half its line
width. Reporting that would bury a real finding — a title block sitting in the
margin — under thousands of edge-touching strokes, so the default ignores a
0.25 pt fringe. Raising --tolerance does not find less; it looks less.

A file it cannot read is reported, never counted clean

"Nothing off-canvas" and "I could not look" are different answers and do not
print the same way.


Also in this release: everything from v0.51.0 forward is unchanged; this is
Pass 294.0 and 294.1 on top of it.

Verified: end to end on one of the operator's own drawings — 9 pages, 234
paths dropped, 247 paths cut, 4,642 off-page glyphs removed; the output
re-scans clean and page 1 renders pixel-identical to the input. CI green at
the tagged commit.

v0.51.0 — a PDF Acrobat wrote could not be opened, and now it can

Choose a tag to compare

@KenM76 KenM76 released this 10 Sep 20:11

The headline: a PDF Acrobat wrote could not be opened, and now it can

A blank spacer page with no /Resources — which Acrobat writes, in its own
stamp files, on your own machine — made pdfcer refuse the whole document,
including the pages that were perfect. Every verb that walks the page tree was
affected: render-page, extract-pages, set-page-size, extract-text.

An absent resource dictionary now resolves to the empty dictionary the
standard itself names
for "this page requires no resources" (ISO 32000-1
Table 30), and pdfcer says it did so — page_resources_defaulted=1 on the
render line, a per-page note on extraction. /MediaBox deliberately did not
move: no clause anywhere names a default page size, so any value would be
invented. The line is invention, not strictness.

★ pdfcer's own smallest test fixture had the same shape, so the engine could
not read the minimal file the project ships.

A stamp restyle was eating your stamp's words

Changing a placed stamp's colour silently replaced its custom label with
the stamp name's default — APPROVED FOR CONSTRUCTION became DRAFT.
Measured on a real file, from a control captioned "colour". Fixed, with a
regression test.

If a document was restyled with an earlier build, pdfcer list-annotations
now prints stamp_label= per annotation, which makes a sweep cheap.

Custom stamps: place them, size them, hear about it

  • pdfcer place-stamp puts a stamp collection's artwork on a page as
    vector — a form XObject behind a /Stamp annotation, which is what
    Acrobat writes. Selectable, movable, deletable, and the page's own content
    stream is never touched. --stamp NAME addresses a stamp the way
    stamp-list prints it; --at X,Y places at the artwork's own size,
    --rect fills a box.
  • A placed stamp's label size can be read and changed
    set-text-annot-style --font-size, and list-annotations reports the size
    and where it came from (the author stated it / pdfcer read it off the
    picture / the file contradicts itself).
  • A shrunk or clipped label reports itself, so --stamp-fit shrink and
    clip are usable at last: pdfcer names the size it chose and how many
    characters did not fit.

⚠ A dynamic stamp places its design-time text. Its words come from Acrobat
JavaScript that pdfcer does not run, and the CLI says so when you place one.

Also in this release

  • A /Text or /Stamp annotation that names a standard icon but carries
    no appearance is now drawn from pdfcer's own artwork, as §12.5.6.4 and
    §12.5.6.12 require of a reader.
  • stamp-pack --stamps-from FILE, because a real artwork sheet is 113 pages
    and nobody types 113 --stamp flags.
  • A page-tree failure stops being reported as "every stamp points at
    nothing"
    page=UNKNOWN, and the real cause named.

For anyone building against pdfcer-core

EditSession::place_page_artwork, add_text_annotation_reporting,
stamp_label_parameters, annot::stamp_label_parameters_in, and
TextAnnotStyle::font_size / stamp_fit. Page::resources_defaulted and
Diagnostics::page_resources_defaulted carry the new disclosure. One new
error variant, EditError::SourcePageOutOfRange. Full contract in
docs/core-api/.


Verified: cargo test --workspace 5,309 pass; tools/run-gates.sh green;
every Pass checked against the operator's own Acrobat-authored stamp file
rather than only against fixtures.

v0.50.0 — a damaged PDF opens, and a redaction stops leaking

Choose a tag to compare

@KenM76 KenM76 released this 10 Sep 04:48

v0.50.0 — a damaged PDF opens, and a redaction stops leaking

Ten Passes since v0.49.0. Two changed a posture rather than a behaviour.

A PDF WITH ERRORS OPENS (Pass 283.0, decision 145, R248). Six malformation
classes that used to cost the whole document now open instead: a duplicate
dictionary key, a missing or unusable stream /Length, a missing endobj, an
object that will not parse, an xref/body disagreement, an unreadable object
stream. Every decision is recorded with what it chose BETWEEN, and
--on-malformed takes the other one. Fail-clean never meant refuse.

REDACTION SWEEPS THE FILE, NOT THE GRAPH (Pass 284.0/285.0, decision 146,
R249). Carriers were found by navigating the document graph while the
writer emits objects by enumerating the cross-reference table; everything
in the difference was copied through verbatim while the report said
"scrubbed". Measured at 12 of 57 files on a real drawing set. A new
fourteenth carrier sweeps every object by EVIDENCE -- never by computing
reachability, which fails silently.

Also: /Ink strokes are editable per point and per stroke; hybrid-reference
files are rewritten so redaction reaches them; run_repertoire answers which
characters a run accepts before the first keystroke; a sticky note declines
a resize as what it is; and the redaction report carries the words a mark
covered rather than one entry per show operator.

v0.49.0 — scrambled CAD text fixed, and four silent losses in markup

Choose a tag to compare

@KenM76 KenM76 released this 08 Sep 16:51

Scrambled text on CAD drawings, and four silent losses in markup

The headline: a whole class of drawing renders correctly now

Reported against a real 2013 SolidWorks drawing set: "Text on this sheet is
scrambled in pdfcer, but appears fine in acrobat reader."
Then, minutes
later, the observation that located it in one step: "when I go to edit the
text the text in the editor box shows correctly."

Reading the text was right; drawing it was wrong. 59 3/4" painted as
@ U / M@-shaped nonsense. The geometry was perfect throughout, which is why
it read as scrambled rather than broken. On page 1 alone, 961 glyphs
had no usable mapping.

For fonts flagged a certain way, the PDF standard says to pick glyphs by
their numeric code, straight out of the font's own table. pdfcer was
picking them by name instead. On a subsetted drawing font that does not
merely fail — the name route lands on a real, valid, unrelated glyph, so the
page paints confident garbage:

what the file said what should be drawn what pdfcer drew
three 3 U
four 4 V
six 6 @
one 1 M
nine 9 nothing

Acrobat was right and we were wrong, in a spot where the standard's printed
text and the way every real reader behaves have drifted apart. Unmapped
glyphs on that drawing went from 961 to zero
, across all 24 sheets.

⚠️ If you have a drawing that renders as garbage but whose text copies out
clean
, that was this bug. It is the signature.


Copy-paste gave you back a different mark than the one you copied

Copying a markup annotation and pasting it dropped four things you can
see:

what was lost what you saw
the border dash a dashed revision cloud came back solid
the opacity a 50 %-opacity highlight came back opaque
the note text a comment came back blank
the author and unsigned

Nothing reported it, and nothing could have: from the paste's point of view it
was drawing a fresh mark, and a fresh mark has no properties to report losing.

An annotation's blend mode was deleted on every restyle

Change a shape's colour and pdfcer deleted its blend mode. If you had set
Darken in Acrobat you got Normal back, were told nothing, and the page
composited differently from then on.

The standard as printed lists that key among the ones a reader may ignore.
The approved corrections removed it from that list — so the printed text
and the corrected text disagree about this exact key, and only the corrected
reading is the operative one.

A "contents locked" annotation could still have its comment overwritten

PDF has a flag whose entire job is "do not allow the contents of this
annotation to be modified."
pdfcer could read it, write it, and list it — and
no part of the program had ever checked it.

⚠️ Worse: the command-line help had been telling you it worked. It does now.

It stays carefully distinct from the other lock flag, which forbids almost
the opposite things — one stops deletion and moving while allowing the comment
to be edited; the other stops the comment while allowing deletion and moving.
Each refusal names the flag actually stopping you, because naming the other
one would send you to clear something that was never in your way.

A clipboard from the previous version would have been mis-read

Caught before this release rather than after. The copy-paste fix above
added a second piece of data per annotation without bumping the clipboard
format's version. A copy made by v0.48.0 and pasted into this build would
have had its second annotation swallowed into its first, and everything after
that mis-read — with no error, because the new field was deliberately built
never to fail.

Fixed with version gating, so an ordinary copy still declares the old version
and still pastes into a build you have open in another folder.


Housekeeping. One of pdfcer's own consistency checkers was found to be
blind to broken text in test assertions; widening it uncovered three real
broken messages, one shipped for weeks.

Verified: fmt, clippy -D warnings with CI's own flags, and all 21
document gates; ~6,980 tests across every package, zero failures, plus the
WebAssembly target, the fuzz harnesses, the no-default-features build and the
no-GUI-dependency invariant; CI green on the tagged commit; the portable
folder copied to a fresh path and the drawing re-rendered there.

sha256 of the zip is in the accompanying .sha256 file.

v0.48.0 — a form field's font, size and colour, settable at last

Choose a tag to compare

@KenM76 KenM76 released this 08 Sep 11:44

A form field's font, size and colour are settable at last

pdfcer edit-field form.pdf --name total \
    --font times-bold --font-size 14 --font-color 1,0,0 -o out.pdf

/DA (ISO 32000-1 §12.7.3.3) was readable since the forms layer shipped and
nothing could write it — and the value pdfcer wrote at field creation was
hard-coded /Helv 0 Tf 0 g. So every field pdfcer had ever authored was
black Helvetica, auto-sized, with no way to say otherwise. This is the last
and largest of the four field properties that were readable and unwritable.

  • --font — any of the 14 standard faces, by hyphenated lower-case name
    (helvetica-bold-oblique, times-italic, courier, zapf-dingbats…)
  • --font-resource — a face the document already embeds, by its resource key
  • --font-size — points; 0 is auto-size, what Acrobat calls Auto and
    what a new text field defaults to
  • --font-color — 1 component for gray, 3 for RGB, 4 for CMYK

Choosing a font is not just writing a name

/DA names a font by a resource key, and that key must resolve in the
form's /DR /Font dictionary. A key that does not resolve never fails
loudly
— the reader substitutes a face, the field looks entirely normal, and
it is drawn in something nobody chose.

So the two cases are split by what pdfcer can actually promise:

pdfcer's guarantee
--font (standard 14) Authors the resource, under Acrobat's own short keys (Helv, TiRo, Cour, ZaDb…), reusing one that is already there. Cannot fail for want of a resource.
--font-resource Checks only. Refused by name if the key is absent — and the refusal lists what is available, because a refusal that is correct and useless is a bug in itself.

Setting it redraws the field

Writing /DA alone would leave the field claiming one face and drawing
another. Changing any of the three regenerates the baked appearance, so the
file and the pixels agree.

⚠️ One stated limit. A --font-resource face is one pdfcer cannot
measure, so auto-size metrics fall back to Helvetica while the name written is
yours. An embedded face with wider glyphs will auto-size slightly differently
from what a reader finally draws.

Colour here is the text, not the box

--font-color is the colour of the value's glyphs. The box's own fill and
border are edit-widget --background and --border-color — a different
dictionary, set by a different verb, and the likeliest way to end up with a
field that looks nothing like intended.


Verified: fmt, clippy -D warnings, check-core-api-verbs and the
document/CI gates green; CI green on the tagged commit; the portable folder
copied to a fresh path and the feature reproduced there at byte level
setting Courier-Bold 12pt blue produces /CoBo 12 Tf and 0 0 1 rg in the
drawn appearance stream, with both refusals firing as documented.

sha256 of the zip is in the accompanying .sha256 file.

v0.47.0 — form-field justification, default values and the no-export flag

Choose a tag to compare

@KenM76 KenM76 released this 08 Sep 10:07

Form-field justification, default values, and the no-export flag

Three properties the read model had exposed since the forms layer shipped
with no writer anywhere in the crate. All three sit on Acrobat's own
field-properties surface.

pdfcer edit-field in.pdf --name total --quadding 2 -o out.pdf
pdfcer edit-field in.pdf --name total --default-value "0.00" -o out.pdf
pdfcer edit-field in.pdf --name secret --no-export true -o out.pdf
Key Was Now
/Q — justification (Table 233) Field::quadding readable, nothing wrote it --quadding 0|1|2, --clear-quadding
/DV — default value (Table 228) Field::default_value readable, nothing wrote it --default-value, --clear-default-value
Ff bit 3 — NoExport (Table 226) defined and referenced nowhere else in the workspace --no-export true|false

/DV matters more than it looks

reset_form reads /DV and removes /V where there is none — so with no
writer, a reset could only ever restore defaults some other application had
authored.
A form pdfcer built from scratch reset every field to empty
whatever its author intended, and there was no way to say otherwise.

/DV's type follows /V: a name for a /Btn (an on-state or /Off), a
text string elsewhere. Writing a string onto a check box would produce a
default no reader could match against an appearance state.

Absent, set, and removed are three different things

/Q and /DV distinguish all three. Table 233 defaults /Q to left, so
setting it to 0 and removing it render identically — and are different
facts about the file. A round trip has to preserve which one it met, which is
why --clear-quadding exists alongside --quadding 0 and why the test checks
the raw dictionary rather than the resolved model.

A refusal rather than a clamp

A /Q outside 0..=2 is refused by name and validated before anything is
written, so a bad value changes nothing. Clamping 7 to 2 would silently
right-align a field you meant to do something else with.


Also fixed: a garbled message, and the check that called it clean

The fresh-folder smoke test for this release found a ten-space gap baked
into an operator-facing error message
in the shipped binary — from a line
continuation lost while the error was being written.

check-string-gaps had reported PASS on it. Its prose-mode rule was a
hand-written list of characters, and the one after the gap was an opening
parenthesis
— the most ordinary way an English sentence introduces a gloss,
absent from the list for no reason anybody chose.

The gate's own comment, written after an earlier miss, had already concluded
that the rule "should be read as anything that starts a word, not as a
list"
— and then implemented a list. It now does what its comment says,
measured before widening (20 findings across all literals, all but one
deliberately-aligned report columns; 1 scoped to prose, the defect) and proved
by re-introducing the defect and watching the gate name it.


Verified: fmt, clippy -D warnings, check-core-api-verbs and the
document/CI gates green; CI green on the tagged commit; the portable folder
copied to a fresh path and justification, default value, no-export, the
reset-restores-the-default round trip and the corrected refusal message all
reproduced there in the shipped binary.

sha256 of the zip is in the accompanying .sha256 file.

v0.46.0 — six check-box tick styles, and two annotation gates enforced at last

Choose a tag to compare

@KenM76 KenM76 released this 08 Sep 05:17

Six check-box tick styles, and two annotation gates that were documented but not enforced

Check boxes and radio buttons get Acrobat's six styles

check · cross · star · circle · square · diamond

pdfcer add-check-box in.pdf --name agree --page 1 --rect 40,40,60,60 \
    --check-style star --no-tooltip -o out.pdf

pdfcer draws each as vector artwork and Acrobat does not, and that is a
deliberate divergence rather than an implementation shortcut. Acrobat's own
appearance stream selects a ZapfDingbats font and shows a glyph, so its tick
depends on resolving that font at display time — and Acrobat and Reader have a
long-standing, recurring bug failing exactly that, which leaves the box blank.
Paths need no font, no /Resources entry and no substitution.

The style is still recorded in /MK /CA as the single ZapfDingbats
character Acrobat stores, so any other editor reads the operator's choice back
into its own picker. Those character codes were derived from Adobe's own
ZapfDingbats.afm metrics and the Adobe Glyph List, and independently
reproduced by a second method before being accepted.

The style survives a resize. The appearance is rebuilt from geometry, so
the rebuild reads /MK /CA back — without that, a star would silently
become a tick on the first drag while the file still said star.

An annotation's /F flags can be set at last

pdfcer set-annotation-flags in.pdf --page 1 --index 0 --print --locked -o out.pdf

AnnotFlags had eight read accessors and no writer. An operator could see
that a markup was hidden and not un-hide it, could see it would not print and
not make it print, and could not lock anything — which made pdfcer's own
Locked gate unreachable from pdfcer.

Takes the whole flag word rather than per-bit toggles: Table 165's bits
interact (--no-view with --print means prints but is not on screen, a
combination reached deliberately), so a toggle-one-bit interface lets a
sequence of individually-sensible calls build a state nobody chose. Refuses a
form widget by name — that is edit-widget --visibility's key.

Two defects, both found by audit, both fixed as classes

The Locked flag was inverted in effect. ISO 32000-1 §12.5.3 Table 165 bit
8 says a locked annotation's properties "including position and size" shall
not be modified. move, resize, rotate and set_annotation_rotation all
ignored it while set_markup_style, reshape and the deletion guards
honoured it — so a locked markup could not be recoloured and could be
dragged anywhere
. LockedContents (bit 10) deliberately still does not
block a transform; it guards the text.

Five verbs documented an encryption refusal none of them performed.
rotate_annotation, set_annotation_rotation, resize_annotation,
move_annotation and set_markup_note each listed
EditError::DocumentEncrypted in their # Errors section with no code path
able to produce it. They enforce it now, and the guard runs before subtype
routing so an encrypted document is named as such rather than being answered
with "use rotate_widget instead".

Widget fill and border colours round-trip

/MK /BG and /BC had read and write on opposite keys of one
dictionary
: pdfcer wrote /BC (hard-coded black, never settable) and never
read it; read /BG and never wrote it. Neither survived a round trip.

pdfcer edit-widget in.pdf --name agree --background 1,0,0 --border-color none -o out.pdf
pdfcer list-fields out.pdf --widgets        # background=1,0,0 border_color=none

none writes Table 189's empty array, which states no colour and is not the
same as the key being absent. CMYK is passed through as CMYK, never converted.
⚠️ pdfcer's own renderer does not paint /MK colours (they are the
named-not-painted case); the values are in the file for viewers that honour
them.


Verified: fmt, clippy -D warnings, and nineteen document/CI gates
green; CI green on the tagged commit; the portable folder copied to a fresh
path and all six styles, the colour round trip, the widget refusal and the
lock-then-refuse sequence reproduced there in the shipped binary.

sha256 of the zip is in the accompanying .sha256 file.

v0.45.0 — rotation composes, and its angle is readable

Choose a tag to compare

@KenM76 KenM76 released this 07 Sep 20:25

Rotation composes now, and an annotation's angle can be read

Two fixes to annotation rotation, shipped together because they interlock.

Turning something repeatedly no longer grows it

Rotating an annotation four times by 15° drew it 1.93× wider and 1.42×
taller
than rotating it once by 60°. Every additional turn multiplied it
again.

The rectangle bounding an annotation was derived from the previous
rectangle. That is correct for one rotation and wrong for every one after it:
the second turn bounded an already-enlarged box while the artwork's own matrix
had only accumulated the angle, and ISO 32000-1 §12.5.5's placement step then
scaled the artwork up to fill the surplus.

The rectangle is now derived from the artwork, through one of three rules,
and rotate-annotation reports which one it used (rect_derived=):

rule when composes?
artwork the annotation has an appearance stream yes
geometry no appearance, but /L /Vertices /QuadPoints /CL /InkList yes
previous-rect neither no — see below

N turns totalling θ now produce the same drawn size as one turn of θ,
asserted to a placement scale of exactly 1.0 after 1, 2, 4, 8 and 24 turns.

One case is still not composable and now says so. An annotation with
neither an appearance stream nor rotatable geometry — a /Square or /Circle
with no /APis its rectangle, and §12.5.2 requires that rectangle
upright, so there is nowhere in the file its orientation could be recorded. It
genuinely does still grow on each turn. The CLI warns, and the API reports it,
so a caller can refuse rather than damage the object.

An annotation's rotation angle can now be read, and set directly

Nothing exposed an annotation's orientation, so it could be written and never
read back — a properties field would have had to display a guess, and a
selection outline could only be an upright box visibly larger than a turned
object.

  • Annotation::appearance_matrix — the appearance's /Matrix, raw.
  • Annotation::appearance_rotation_degrees() — the angle, anticlockwise.
    Returns nothing for a shear or a mirror: those are not angles, and reporting
    the nearest one would seed a field the operator is about to commit.
  • EditSession::set_annotation_rotation() and rotate-annotation --absolute
    — set the angle absolutely rather than by a delta, which is what a typed
    properties field needs. Idempotent. Refuses rather than assuming zero
    when the current angle cannot be read.
  • pdfcer_render::annot::appearance_placement() — §12.5.5's placement
    quadrilateral, made public, so a shell can draw a selection outline in the
    object's own orientation instead of re-implementing a normative algorithm.

Also

The verb's own documentation claimed "the artwork does not grow; only the
rectangle that bounds it does"
. That was true of one rotation and false of
the next. All three copies are corrected, with the superseded wording kept
legible rather than quietly deleted.


Verified: fmt, clippy --all-targets --all-features -D warnings, the
wasm32 target check, the no-GUI/no-network dependency check, and all
document/CI gates green; CI green on the tagged commit; the portable folder
copied to a fresh path and the fix reproduced there.

sha256 of the zip is in the accompanying .sha256 file.

pdfcer 0.44.1

Choose a tag to compare

@KenM76 KenM76 released this 07 Sep 18:12

Patch release. Fixes two defects in 0.44.0's set-text-annot-style, both reported by the pdfcer-gui project within hours of that release.

Single-folder portable Windows x64 build. Unzip and run pdfcer.exe.

Changing a text box's colour un-wrapped it

A multi-line /FreeText collapsed to a single line when only its colour was changed — silently, destroying layout the operator had.

/FreeText carries no multiline flag (§12.5.6.6 defines none), so the value has to be measured by re-baking the annotation's own text both ways and comparing against the bytes on disk. set-markup-note did that correctly; set-text-annot-style shipped without it.

The fix is one shared helper both verbs call, rather than a second correct call site — the property that failed was two re-bakers of the same subtype disagreeing about a value neither can read.

That measurement also answers a second question, which is now reported instead of discarded: when neither layout reproduces the bytes on disk, the appearance was authored by something other than pdfcer, and re-baking replaces it. set-text-annot-style now prints was_foreign=1 in that case rather than overwriting a designer's stream in silence.

Changing a sticky note's colour rewrote its icon

§12.5.6.4 lists seven icon names and then says "Additional names may be supported as well", so a producer's own icon name is conforming — and pdfcer rewrote it to /Note when an operator changed the note's colour. Somebody else's content, altered by a control that says nothing about icons.

StickyIcon::Other now carries the name verbatim. This costs nothing in the drawing: pdfcer paints the same glyph for all seven icons, so an unmodelled name renders exactly as a modelled one.

Breaking (library consumers only, one day wide)

Both were made public in 0.44.0, yesterday:

  • StickyIcon is no longer CopyOther owns its bytes.
  • StickyIcon::name() takes &self and is no longer const.

Also

rotate-annotation's help and two doc comments claim "the artwork does not grow". That claim is flagged as under investigation, because the operator reported the opposite — the object grows with each application of the tool. It is correct for a single rotation of an unrotated annotation; the repeated case is being measured. Flagged rather than deleted, because a disclosure that quietly disappears is worse than one that says it is in doubt.

Full detail in docs/ROADMAP.md (Pass 253.5).

pdfcer 0.44.0

Choose a tag to compare

@KenM76 KenM76 released this 07 Sep 01:11

Single-folder portable Windows x64 build. Unzip and run pdfcer.exe — no installer, no registry writes, no network access required.

This release clears every open request from the pdfcer-gui project.

Comment review, end to end

  • Replies. A comment thread could be read and never added to. add-reply writes /IRT + /RT /R, on the parent's page, in the parent's colour so a thread reads as one conversation.
  • Review status — Accepted / Rejected / Cancelled / Completed / None, and the Marked model. Three things here came from reading ISO 32000 §12.5.6.3 rather than assuming, and each changed the design: /State and /StateModel are text strings, not names; /StateModel is derived from the state so the one non-conforming pairing cannot be expressed; and a second status by the same author must reply to that author's previous status, not to the annotation. That last is a shall, and getting it wrong is invisible — a star of status annotations renders identically to a correct chain in every viewer. attached_to and chain_depth are reported so it is checkable.
  • No resolver for which status is "current", deliberately: the standard says nothing about ordering, and /M is optional and empirically ties. Shipping none is spec-correct.
  • Sticky note icon and colour are editable in place (set-text-annot-style), keeping the note's object identity — delete-and-replace was the only route before, which lost /M, the object id and any reply.
  • /C and /Name are readable on every annotation (list-annotations prints color= and icon=). A colour swatch or a sort-by-colour was impossible purely because nothing could read the current value.
  • Pop-up open/closed state (set-annotation-open) — pdfcer had written /Open since Pass 6.2 and could read back neither copy.

A text file becomes a PDF

place-text paginates a .txt into as many pages as it needs — the missing other half of text export. This required a page-creation primitive the crate never had; everything before could only copy an existing page.

Unmappable characters refuse the import and name every one rather than dropping them silently (--drop-unmappable is an explicit opt-in that reports each lost code point). U+000C is a hard page break, matching extract-text's own separator, so text out and back in keeps its pagination.

CAD drawings

The hairline / "line weights off" display mode now reports how many strokes it actually thinned, so "no visible change" is distinguishable from "the setting isn't working". A page whose strokes are already sub-pixel renders byte-identically with the mode on or off and reports zero — the mode is a ceiling, not a set.

Also

Annotation gains open, color, icon, state and state_model. StickyIcon gains from_name and a public name.

Full detail in docs/ROADMAP.md (Passes 252.0, 253.0–253.3, 254.1).