Repository navigation
textcolour: paint the text one colour, and leave the page alone - #47
Merged
Merged
Conversation
Doc.RecolourText and Doc.UnreachedText, with `pdfops textcolour` over them. This closes BentoPDF's text-color. ⛔ SETTING A COLOUR BEFORE EACH TEXT OBJECT IS NOT ENOUGH, and that is what the first draft did. A text object that sets its OWN colour overrides it, so a page whose paragraphs say "1 0 0 rg" came out unchanged while the tool reported success. Every colour operator inside a text object is blanked now — g, rg, k, cs, sc, scn and their stroking capitals. ⛔ The colour goes OUTSIDE the text object, wrapped in q/Q. A text object does not save the graphics state, so a colour set inside one leaks into whatever is drawn after it — which is how a recolouring turns a company's logo the same shade as its paragraphs. A test asserts the rectangle beside the text keeps its own red. Both the filling and the stroking colour are set, because text has render modes. ⛔ IT SPLICES RATHER THAN RE-SERIALISING, which is why go-pdfkit/reader v0.8.0 was needed first: an Operation now says which bytes of the stream it is. Writing the operations back out would mean formatting every operand again, including an inline image's still-encoded bytes, where a formatter that is merely lossless-for-objects can change what the image MEANS. A stream with no text comes back byte for byte unchanged, and the colour operators are blanked with SPACES rather than deleted so every offset after one is still the offset the scan reported — deleting would be correct only if the edits were applied back to front, and a rule correct in one order only is a rule somebody will reorder. ⛔ WHAT IT CANNOT REACH, IT COUNTS. Text inside a Form XObject is not recoloured, and not because it would be hard: an XObject is SHARED, so recolouring one would change pages the caller did not name. A document whose text is all inside XObjects would otherwise come back looking converted and be unchanged — the one failure a caller cannot see by opening the result and glancing at it. ⛔ AND A COMPOSITION DEFECT CAUGHT BEFORE IT SHIPPED: `decorate` rebuilt a page's Contents from the SOURCE's parts, so a page both recoloured and watermarked came back in its original colours with nothing failing. It uses the rewritten stream when there is one. The mutation restoring the old behaviour is caught. #f00 is FULL red: each digit of a short hex colour is doubled, not shifted, so the component is 0xf*17. A shift gives 240/255 for what every browser calls full red, and a logo recoloured to 94% red is a defect nobody can see but a printer. The test asserts the short and long forms agree. A page this package MADE rather than borrowed is skipped: its content is the tiles and marks this document composed, and a stamp's colour is the stamp's own business. One guard was removed as dead code a coverage gap found: a check for overlapping edits, which cannot happen because reader asserts its ranges are ordered and disjoint. The invariant is named where it is established. 100% statement coverage with zero uncovered blocks, go vet and gofmt clean. Seven mutations caught. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
textcolourpaints the text one colour and leaves the rest of the page alone.pdfops textcolour -to '#0000ff' report.pdf blue-text.pdf pdfops textcolour -to black -pages 1-4 scan.pdf fixed.pdf⛔ Setting a colour before each text object is not enough, and that is what
the first draft did. A text object that sets its own colour overrides it, so
a page whose paragraphs say
1 0 0 rgcame out unchanged while the toolreported success. Every colour operator inside a text object is blanked —
g,rg,k,cs,sc,scnand their stroking capitals — so the colourasked for is the one that paints.
⛔ The colour goes outside the text object, wrapped in
q/Q. A textobject does not save the graphics state, so a colour set inside one leaks into
whatever is drawn after it — which is how a recolouring turns a company's logo
the same shade as its paragraphs. A test asserts the rectangle beside the text
keeps its own red.
Both the filling and the stroking colour are set, because text has render
modes: 0 fills, 1 strokes, 2 does both. A tool that set only the fill would
leave outlined text in its old colour.
⛔ It splices, and does not re-serialise
Writing the operations back out would mean formatting every operand again,
including an inline image's still-encoded bytes, where a formatter that is
merely lossless-for-objects can change what the image means. So the
rewriting keeps every byte it does not deliberately change — which is why
reader.Operationcarries its byterange, added for this. A stream with no text comes back byte for byte
unchanged.
The colour operators are blanked with spaces rather than deleted, so every
offset after one is still the offset the scan reported. Deleting would be
correct only if the edits were applied back to front, and a rule that is
correct in one order only is a rule somebody will reorder.
⛔ What it cannot reach, it counts
Text drawn through a Form XObject is not recoloured, and the reason is not
difficulty: an XObject is shared, so recolouring one would change pages the
caller did not name. A document whose text is all inside XObjects would
otherwise come back looking converted and be unchanged — the one failure a
caller cannot see by opening the result and glancing at it. So the count is
reported:
Doc.UnreachedTextis the same answer in Go, and it is zero until the documenthas been written because until then there is nothing to have reached.
#f00is full redEach digit of a short hex colour is doubled, not shifted:
#f00isff0000, so the component is0xf × 17. A shift gives 240/255 for what everybrowser and design tool calls full red, and a logo recoloured to 94 % red is a
defect nobody can see but a printer. The test asserts the short and long forms
agree, and says the wrong number out loud so nobody has to work out which is
which.