The returned bool should be true if the event was consumed, aka if we
should ignore it. But `dispatch_event()` returns true if
you *shouldn't* ignore it, so we have to invert that return value.
And make it capable of printing to any Core::Stream.
This is useful on its own and can be used in a number of places, so move
it out and make it available as JS::print().
This implements the fastest seeking mode available for tracks with cues
using an array of cue points for each track. It approximates the index
based on the seeking timestamp and then finds the earliest cue point
before the timestamp. The approximation assumes that cues will be on
a regular interval, which I don't believe is always the case, but it
should at least be faster than iterating the whole set of cue points
each time.
Cues are stored per track, but most videos will only have cue points
for the video track(s) that are present. For now, this assumes that it
should only seek based on the cue points for the selected track. To
seek audio in a video file, we should copy the seeked iterator over to
the audio track's iterator after seeking is complete. The iterator will
then skip to the next audio block.
This adds two options:
- An option to print a specific track number only, and omit all others.
- An option to print each block for each track that is printed.
Tracks have a timestamp scale value that should be present which scales
each block's timestamp offset to allow video to be synced with audio.
They should also contain a CodecDelay element and may also contain a
TrackOffset that offsets the block timestamps.
The fast seeking toggle is in the new Playback menu, and when enabled
it makes the PlaybackManager immediately start playing after finding a
keyframe to decode.
Now that we're able to find the nearest keyframe, we can have a fast
seeking mode that only seeks to keyframes, so that it doesn't have to
also decode inter frames until it reaches the timestamp.
The default is still accurate seeking, so that the entire seeking
implementation can be tested.
This just searches sequentially through each block in a SampleIterator
until it finds a block after the specified seek timestamp. Once it
finds one, it will try to set the input/output iterator to the most
recent keyframe. If the iterator's original position is closer to the
target, however, it leaves it at that original position, allowing
callers to continue decoding from that position until they reach the
target timestamp.
This implements the PlaybackManager portion of seeking, so that it can
seek to any frame in the video by dropping all preceding frames until
it reaches the seek point.
MatroskaDemuxer currently will only seek to the start of the video.
That means that every seek has to drop all the frames until it comes
across the target timestamp.
The PlaybackManager::update_presented_frame function was getting out of
hand and adding seeking was making it illegible. This rewrites it to be
(hopefully) quite a bit more readable, and adds a few comments to help
future readers of the code.
In addition, some helpful debugging prints were added that should help
debug any future issues with the player.
With these changes, the seek bar can be used, but only to seek to the
start of the file. Seeking to anywhere else in the file will cause an
error in the demuxer.
The timestamp label that was previously invisible now has its text set
according to either the playback or seek slider's position.
The Demuxer class was changed to return errors for more functions so
that all of the underlying reading can be done lazily. Other than that,
the demuxer interface is unchanged, and only the underlying reader was
modified.
The MatroskaDocument class is no more, and MatroskaReader's getter
functions replace it. Every MatroskaReader getter beyond the Segment
element's position is parsed lazily from the file as needed. This means
that all getter functions can return DecoderErrors which must be
handled by callers.
Making these functions static makes it easier to implement lazy-loading
since the parsing functions can now be called at any time.
The functions were reorganized because they were not defined in the
order they are called. However, instead of moving every function to
that order, I've declared some but defined them further into the file,
which allows the next commit's diff to be more readable.
Matroska::Reader functions now return DecoderErrorOr instead of values
being declared Optional. Useful errors can be handled by the users of
the parser, similarly to the VP9 decoder. A lot of the error checking
in the reader is a lot cleaner thanks to this change, since all reads
can be range checked in Streamer::read_octet() now.
Most functions for the Streamer class are now also out-of-line in
Reader.cpp now instead of residing in the header.
As new demuxers are added, this will get quite full of files, so it'll
be good to have a separate folder for these.
To avoid too many chained namespaces, the Containers subdirectory is
not also a namespace, but the Matroska folder is for the sake of
separating the multiple classes for parsed information entering the
Video namespace.
Timers keep their previously set interval even for single-shot mode.
We want all timers to fire immediately if they don't have a delay set
in the start() call.
These actions were being constructed, and they work, but were not shown
in the toolbar. Adding them will allow users to actually use them, as
well as pick up any bugs they might have.
These actions were not updated accordingly when one scrolled through the
document, and thus one could accidentally, for example, move to the next
page when standing on the last, which caused a crash.
This commit fixes that behavior, toggling the actions' enabled status
depending on the new page being displayed.
When removing all contents from the NumericInput box in PDFViewer the
callback set the (empty) text again back in the box, triggering another
callback in a recursive, non-stopping fashion. Not setting the text back
in the box avoids the problem.
Now that the Renderer accepts preferences, PDFViewer can offer ways for
changing these preferences. The first step in this direction is to add a
checkbox that allows toggling whether clipping paths are visible or not.
A Config item has also been added to remember this setting.
A new struct allows users to specify specific rendering preferences that
the Renderer class might use to paint some Document elements onto the
target bitmap. The first toggle allows rendering (or not) the clipping
paths on a page, which is useful for debugging.
The existing path clipping support was broken, as it performed the
clipping operation as soon as the path clipping commands (W/W*) were
received. The correct behavior is to keep a clipping path in the
graphic state, *intersect* that with the current path upon receiving
W/W*, and apply the clipping when performing painting operations. On top
of that, the intersection happening at W/W* time does not affect the
painting operation happening on the current on-build path, but takes
effect only after the current path is cleared; therefore a current and a
next clipping path need to be kept track of.
Path clipping is not yet supported on the Painter class, nor is path
intersection. We thus continue using the same simplified bounding box
approach to calculate clipping paths.
Since now we are dealing with more rectangles-as-path code, I've made
helper functions to build a rectangle path and reuse it as needed.
Previously, shuf exclusively read input from stdin. This PR adds an
option to read from a file using Core::Stream::File. Since a file might
contain arbitrary bytes, including null bytes, this PR represents lines
as Spans of Bytes instead of Strings.
Lasso selection works by allowing the user to draw an arbitrary shape
much like the pen tool and ensuring the shape is closed by connecting
the start/end points when the user is done drawing. Everything inside
the shape becomes the selection.
Selection is determined via an outer flood fill. We begin a flood fill
from a point that is guaranteed to be outside of the drawn shape, and
anything the fill doesn't touch is determined to be the selection
region.
This makes ImageEditor responsible for clearing the active selection
when the escape key is pressed. If the active tool didn't act on the
Escape key (like some selection tools use this to indicate cancelling of
making a new selection), then ImageEditor will check for an active
selection and clear it.
We now can handle dynamic updating of the disabled attribute of a <link>
of the stylesheet type.
We do this by hooking the adding and removing attribute's handlers and
dynamically loading/removing the stylesheet if it has been
enabled/disabled.
An XRef table usually starts with an object number of zero. While it
could technically start at any other number, this is a tell-tale sign
of a broken table.
For the "broken" documents I encountered, this always meant that some
objects must have been removed from the start of the table, without
updating the following indices. When this is the case, the document is
not able to be read normally.
However, most other PDF parsers seem to know of this quirk and fix the
XRef table automatically.
Likewise, we now check for this exact case, and if it matches up with
what we expect, we update the XRef table such that all object numbers
match the actual objects found in the file again.