Open and Place store some images again instead of keeping their bytes:
a photo turned by its orientation, a TIFF the image decoder reads, and
a CMYK TIFF with one plane per ink. Those images lost the ICC profile
their file embeds, and 16-bit samples were cut to 8 bits.
- raster_image keeps gray and RGB samples at 8 or 16 bits in the
stored PNG, with the gray or RGB profile in its iCCP chunk. A
profile of another color space, such as the CMYK profile of a CMYK
image read as RGB, is left out. A TIFF's profile is read with the
tiff decoder, which reads it whatever the image's size.
- stored_cmyk_tiff and turned_cmyk write CMYK8 or CMYK16 as the file
has it, with the CMYK profile as TIFF tag 34675 (UNDEFINED). A CMYK
JPEG's profile is put together from all its APP2 segments.
- The docs/target-app-parity.md line on EXIF orientation says 8 or 16
bits, and a line after it describes the kept profile.
Place, taskbar, opening, Save Options, Missing Fonts and one control
test left their folders in the system temp folder after every run.
Other opening and control tests removed theirs only at the end of a
passing test, so a failing one left its folder.
- A test-only Folder guard (crate::tests_folder) removes its folder
with everything in it when dropped, also when the test panics, as
the find-files tests' Tree guard does (#6948).
- The opening and control tests that made their own folders use it.
- Each Place test gets its own folder instead of one shared per-process
folder, so one test's cleanup cannot remove another's files.
- The Missing Fonts helper no longer removes and remakes the folder
vectorcraft_testkit::temp_dir made, which keeps it owner-only on Unix.
- vectorcraft_testkit::temp_dir is unchanged.
Each measurement of a frame's share started from a copy of the whole frame, its own runs included, and then replaced the runs. The frame is now copied once without its own text, and each measurement copies that. A test counts the frame text copied to measure frames that already hold text.
Clear, path.deleteAnchors, Cut, Copy and Cut Path scanned every selected
anchor or segment of a path once for each of its subpaths, so a path with
thousands of subpaths took time that grew with the square of their count.
The selection sets are ordered by subpath, so a new helper, subpath_refs,
reads one subpath's entries with a range lookup instead. The results are
unchanged.
With one selected interior anchor on each of 8,000 open subpaths of one
path, Clear took a median of 74 ms of thread CPU time before and 3 ms
after (release build, five runs each; the timings are noisy because the
CPU was shared and loaded).
Tests in delete_path_pieces.rs check exact surviving subpaths and their
order for anchor and segment selections across open and closed subpaths,
an untouched subpath, references to missing subpaths and indexes, one
Undo/Redo step and a second Clear, and Cut Path on several subpaths.
Without a plug-in store, plugin.install read the process grant once
for its later check and again for its approval. A grant another session
recorded between the two reads approved the module; when that session
revoked it before the install, the check saw the grant it first read
and the install recorded the revoked grant again. The approval now comes
from the first read.
plugin.install published the plug-in before it wrote the module and its
grant to the Plug-ins folder. A plugin.remove in another session in
between removed the plug-in, its file and its grant, and the install then
wrote both back, so the plug-in loaded with its grant at the next launch.
A session attaching the folder in between loaded the old version, which
kept running while the folder held the new one. plugin.remove, loads of
the stored plug-ins and loads of a plug-ins folder now wait until an
install has written the folder, and an install waits for them.
Docs: docs/plugins.md, docs/target-app-parity.md.
Object > Path > Remove Anchor Points removed each selected anchor with
its own Vec::remove, shifting the rest of the subpath every time, so
removing k anchors from an n-anchor subpath took O(k*n) moves.
- pathops: remove_anchors removes a subpath's anchors the last first,
reading each anchor's current neighbors, handles, length and closed
state as removing them one at a time does, and compacts the anchor
list once. remove_anchor and remove_anchors share one refit, so the
result is bit for bit the same.
- engine: the command groups the selected anchors by subpath, works out
the next selected anchor with each subpath reading only its own
selected anchors, and takes emptied paths out of the selection
together (Selection::remove_all).
- Tests compare remove_anchors bit for bit with remove_anchor over 600
generated subpaths and focused cases, count the anchors moved (2001
anchors, every other one removed: at most one move per anchor left,
500,500 one at a time), and compare the next-anchor selection and
the command on many paths and subpaths with removing one at a time.
Assigning to a named child counts its list of the matching children, and
assigning through an empty XMLList counts the chain of targets it climbs,
before either changes the tree. copy(), toXMLString(), normalize(), the
`..` selector and the depth check on insertion hold the stack they keep
for each open level of the tree. Inserting an XMLList, and assigning one
to an item of an XMLList, hold their copy of the list's items.
copy() reads attributes in place, and toXMLString() reads attributes and
namespace declarations in place, instead of copying the lists. copy()
charges the declarations it copies before it copies them. Deleting by
name or assigning an attribute compacts the remaining links in place
instead of building a second list.
toXMLString() holds the set of prefixes a namespace lookup passes, and
picks a new nsN prefix in one pass over the declarations in scope
instead of rescanning them for each candidate.
The parser holds its open elements and their names, the text it gathers,
each tag's attribute values, the sets that check declarations and
attribute names, and its one copy of an element's namespaces, and it
charges the strings of the namespace declarations it makes. The
allowance it holds from the source's length covers the copy a value
takes as it becomes a string.
Comparing XML values, converting them to strings, assigning an XMLList
to an attribute, and reading a name, a qualifier, a namespace URI or
embedded text from a string built by joining strings flatten it with
flat_charged. An attribute name read from a string reserves its copy,
and an element name shares the string.
Docs: docs/target-app-parity.md.
An <image> whose EXIF orientation or TIFF Orientation tag turns or flips
its pixels, linked or as a data URL, is turned before usvg reads it: the
pixels are stored as they are shown, the width and height swap, and the
box fits the turned image under preserveAspectRatio, as CSS
image-orientation: from-image describes. A turned CMYK JPEG or TIFF keeps
its inks as a CMYK TIFF, as does a CMYK TIFF that is not turned.
- Data URLs of type image/tiff, and untyped TIFF bytes, are read.
- TIFFs with extra channels, CCITT Group 3 TIFFs and planar CMYK TIFFs
are read as Open and Place read them.
- Converted images decode within the decode allowance, and a TIFF that
claims more strips or tiles than the decoder can count is refused
before any decoder opens it.
- exif_orientation moves to vectorcraft-doc so Open/Place and SVG import
share it.
Tests: crates/svg/tests/it/image_orientation.rs.
Spine::at_length walked the first subpath's segments from the start
until one ended at or past the arc length, so placing every blend step,
spread key, blend widget and Objects on Path attachment cost one
comparison per earlier segment. It now searches the anchors' arc
lengths, which never decrease, and picks the same segment: the first
whose end is at or past the length, so an exact anchor length and
repeated lengths from zero-length segments keep their segment. The
point and tangent evaluation is unchanged.
Tests compare the search with the old walk on open and closed spines,
one segment, curves, zero-length segments, every anchor, both ends,
out-of-range and non-finite lengths, and check that a 4,096-segment
spine needs at most 13 comparisons.
193 Hindi rows outside the plug-in messages said something other than
their English: "a number of points" read as "many digits", swatches as
clocks, crop as a harvest, resolution as a resolve, and saving used
words the glossary replaces. Others left English prose in place,
dropped a clause ("to keep them all", "what follows it") or ran the
sentence backwards.
- Rows use सेव, स्वॉच, क्रॉप, रिज़ॉल्यूशन, पाथ and टाइप (for a text
object) as docs/localization-hi.md sets them.
- Values a command reads (enum values, preset names, OpenType tags)
stay as the English writes them.
- A test checks those terms and the point unit in every Hindi row, and
another pins rows whose quantity or clause was lost.
- docs/localization-hi.md, docs/localization-parity.md and
docs/target-app-parity.md carry the new counts and a wording note.
The previous commit took the spans from how far each piece bends away
from the split whenever both handles there were within ONE_CURVE of the
anchor. A curve that barely moves at the split but doesn't bend there
(a straight one, say) has short handles and no bend, so that gave 0/0
and the side fell back to the polygon shares. Now the spans come from
whichever pair is the longer: the handles, or the bends (which go with
the squares of the spans). The check of every piece against the
rebuilt curve is unchanged.
Test: a_split_curve_keeps_the_top_object_patch_where_it_barely_moves_and_runs_straight.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A justified area type line counted a tab as a word space, so it gave
the tab a share of its room that no glyph drew, and the line ended
short of its measure. A word-tab-word line ended short too.
- The text up to a line's last tab keeps its spacing, each tab keeps
its stop and its leader stays in its gap. The text after the last
tab takes the line's room: word spacing, letter spacing and glyph
scaling within their ranges, then its word spaces, or the gaps
between its letters when it has none. Kashidas go in that text when
it is one run.
- A tab separates two words, so Single Word Justification does not
align a word-tab-word line.
- Right-to-left lines take the text drawn after the last tab drawn,
measured as the line is drawn, and vertical lines work down the
column. Point type keeps its natural width.
Docs: docs/target-app-parity.md, docs/type-parity.md.