summaryrefslogtreecommitdiffstats
path: root/libass/ass_parse.c
Commit message (Collapse)AuthorAgeFilesLines
* ass_render: move TextInfo into RenderContextrcombs9 days1-1/+1
|
* ass_parse: take RenderContext* in parse_vector_clip()rcombs2022-11-151-6/+6
|
* ass_parse: take RenderContext* in process_karaoke_effects()rcombs2022-11-151-8/+9
|
* ass_parse: take RenderContext* in parse_tags()rcombs2022-11-151-186/+186
|
* ass_render: take RenderContext* in reset_render_context()rcombs2022-11-151-2/+2
|
* ass_parse: take RenderContext* in get_next_char()rcombs2022-11-151-2/+2
|
* ass_parse: take RenderContext* in apply_transition_effects()rcombs2022-11-151-16/+18
| | | | Also remove unneeded event arg
* ass_parse: take RenderContext in update_font()rcombs2022-11-151-9/+10
|
* refactor: prefix all internal API with ass_Oneric2022-10-221-15/+15
| | | | | | | | | | | | | | | If static libass is linked into a binary defining functions of the same name there will be issues. To avoid this use an ass_ prefix for namespacing. Before this commit we already did this for most but not yet all internal API. read_file is renamed to ass_load_file as ass_read_file already exists as a public API function. All other functions are simply prefixed with ass_. Fixes: https://github.com/libass/libass/issues/222 Fixes: https://github.com/libass/libass/issues/654
* parse: replace mult_alpha and change_alpha exportsOneric2022-10-221-2/+9
| | | | | | They are both used only once outside of ass_parse.c to apply fade, so instead export one function handling VSFilter-compatible fade application.
* refactor: move and static'fy some internal functionsOneric2022-10-221-0/+44
| | | | | | Although declared and defined in ass_utils.{h,c}, those functions are only used in one other file and aren't useful at other places.
* parse: remove unused functionOneric2022-10-211-10/+0
| | | | It has not been used since 7af78014635dbe81c3328405aa68dd2cfef94bc4.
* Implement v4++'s \kt tagOneric2022-10-141-1/+20
| | | | | | | | | | | | | | \kt allows to set the karaoke timing offset to a value other than the sum of preceeding karaoke durations. Notably this means multiple karaoke sequences of one Event can be ative at the same time. Like in VSFilter, \kt is available regardless of the format version. Using \kt after a karaoke tag in the same override sequence always makes the preceeding karaoke act as if already completed. Using \kt within a run resets timing for the next karaoke run. Addresses part of https://github.com/libass/libass/issues/461. Further support for v4++ requires at least an ABI break.
* parse: avoid signed overflow for effect_skip_timingOneric2022-09-291-5/+5
| | | | | | | | | | | | | | | | | | | | | | | | | | | | | Discovered by OSS-Fuzz. For sufficiently large valued or numerous karaoke tags the addition to effect_skip_timing can overflow. Since the operands were signed, this was undefined behaviour. For utmost VSFilter compatibility, we'd like to ideally emulate its effective wraparound behaviour. Although the exact order pf calculation differs, merely ensuring our values safely wraparound too should yield the same end result. To that end, we first change the types of the relevant members to int32_t guaranteeing the same size as in VSFilter and complement-of-two representation and then also cast one operand to uint32_t. Through integer promotion both operands will be treated as unsigned, so any overflow will be (perfectly defined) unsigend overflow. The final implicit cast back to int32_t is technically implementation defined and may raise an "implementation-defined signal", but due to the complement-of-two semantics in pratice we just get a wraparound matching the wraparaound expected for the original signed complement-of-two overflow, but while avoiding UB. Compilers appear to emit the same machine code on x86 with or without the uint32_t cast, if any optimisations are enabled and otherwise at most minor differences. Fixes: https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=47993
* parse: remove useless branchOneric2022-09-251-6/+3
| | | | | | If state.effect_timing is zero adding it does nothing. Also checking for zero and branching is likely at least as costly as adding zero.
* parse: avoid more UB on double to integer castsOneric2022-09-251-2/+2
| | | | | | Omission in 85c8c6d7be14cc2602b92ec715834b9c1069a325. Of course, the same reasoning as in the mentioned commit also applies to (at least) all other karaoke tags.
* Fix legacy effect's delay scaling and precisionOneric2022-09-151-6/+20
| | | | | | | | | | | | | | | | | | | | | Usually the delay parameter of legacy effects is scaled to be relative to the PlayRes canvas. This happens explicitly in VSFilter and automatically in libass. However, this scaling in VSFilter happens _before_ applying max(delay, 1) which means, if e.g. delay=0 it ends up as 1 ms per _storage pixel_. To get the same effect in libass we must explicitly "unscale" the fallback for small or negative delays. VSFilter also casts the scaled delay value to int afterwards, which can lead to noticeable differences if the scaled value isn't an integer. To emulate this in libass we do not want delay to be an int (which would also ruin the unscaling for delay=0), but we need to convert our already PlayRes-relative value to a storage-relative one, then cast to int and finally convert back to a PlayRes-relative value. This rounding error can already be observed after just one second for PlayResX=StorageX/8 and delay=25.
* ass_parse: fix premature truncation on \t(\Xa)rcombs2022-08-141-1/+1
| | | | | | | This caused animations to out-of-range alpha values to be clipped to the uint8_t range _before_ performing the lerp, rather than only after. VSFilter only truncates after. Using e.g. `\1a0\t(\1a1FF)` should run through the entire 0-FF range _twice_.
* parse: avoid UB on double to integer castsOneric2022-04-261-3/+3
| | | | | | | | | | | | | Casting floating point values to an integer type is undefined behaviour if it's not a regular number or the integral part cannot be represented in the integer type. This fixes issues found by UBSAN in libass' public OSS-Fuzz corpus where NAN ("be") or a too large value ("k") was casted to int. Sample IDs (one instance each there are duplicates): OSSFuzz-3617a28ea3900c2603059049ce4c70c01a535a3e OSSFuzz-292a3032ea273cc9dbaaa0a4291dd84e0cc07c65
* parse: replace argtoi with argtoi32Oneric2022-04-261-32/+25
| | | | | | | | | | | | | | | This continues our transition towards fixed-width types to improve VSFilter compatibility regardless of the platform libass is compiled for. Local variables are also switched to int32_t, but struct members are left as is for now. By dropping mystrtoi, which was only used by argtoi, we also fix float-cast-overflow issues in it identified by UBSAN in libass' public OSS-Fuzz corpus. mystroi32 does not suffer from this problem. Sample ID (one instance there are duplicates): OSSFuzz-123cf9a553c5745854037a52e87947721257f1f3fb
* parsing: use string references for font family and drawing textDr.Smile2021-02-221-17/+14
| | | | That eliminates most uses of strdup() in the rendering process.
* process_karaoke_effects: honor info->skip in \kf calculationsOleg Oshmyan2020-10-271-2/+8
| | | | This mainly affects trimmed trailing whitespace.
* Improve \kf position roundingOleg Oshmyan2020-10-271-6/+6
|
* process_karaoke_effects: fill from right to left if rotatedOleg Oshmyan2020-10-271-0/+10
| | | | This matches VSFilter.
* Delay angle conversion to radians until the last momentOleg Oshmyan2020-10-271-4/+1
| | | | | This allows decisions based on the angle values to be slightly more accurate and might save us a few flops.
* Handle \k0 \ko0 \kf0 \K0 like VSFilterOleg Oshmyan2020-10-271-3/+10
| | | | | | | | | | | | | Don't break runs when zero-duration karaoke starts unless the karaoke *type* differs. The zero-duration karaoke block ends up glued to the previous block (if any). In case of subsequent karaoke override tags, like {\k100\k0}, the intervening tags will advance the next karaoke block's start time, but not this combined block's start or end time. Of course, runs may still be broken in the same place if there's another reason for a run break besides karaoke, so zero-duration karaoke blocks can still occur. Run breaks that have no karaoke tags at all also still produce zero-duration karaoke blocks (if there is karaoke at all).
* process_karaoke_effects: use long long for timestampsOleg Oshmyan2020-10-271-3/+3
| | | | | | | | Our frame timestamps are long long. Don't truncate them to int here. This also avoids overflow in (tm_end - tm_start) if \k... tag arguments are big, although, of course, parse_tags still reads them in as doubles to begin with and their conversion from double may still be undefined.
* process_karaoke_effects: compute \kf from glyph advances, not boundsOleg Oshmyan2020-10-271-6/+2
| | | | Both ways make sense, but traditional VSFilter does it this way.
* process_karaoke_effects: place line at infinity except during \kfOleg Oshmyan2020-10-271-9/+8
| | | | Fixes \k and \ko in https://github.com/libass/libass/issues/357.
* process_karaoke_effects: reuse \kf logic for \k and \koOleg Oshmyan2020-10-271-15/+10
|
* process_karaoke_effects: explicitly handle edge cases for \kfOleg Oshmyan2020-10-271-2/+8
| | | | | | | In particular, don't divide by zero given \kf0. This fixes https://github.com/libass/libass/issues/124. The order is important: \kf accepts negative values.
* process_karaoke_effects: honor starts_new_runOleg Oshmyan2020-10-271-4/+10
| | | | | | | | | | | This matches VSFilter. In particular, compared to what we did before, karaoke blocks additionally end when an override tag changes something, as well as on any line break (after the trimmed leading whitespace on the new line) and after any trimmed leading whitespace on the first line. The text that follows the break has a zero karaoke duration, and its karaoke effect starts immediately after the karaoke effect ends for the block before the break.
* process_karaoke_effects: give the code a facelistOleg Oshmyan2020-10-271-50/+38
|
* Handle strdup-fails regardinging FontFamilyOneric2020-10-271-4/+12
| | | | | | | We already ensure at creation that all styles have a non-null FontName. However font family strings are strduped at various places and NULLs cannot be fully avoided, since already the very first font strdup may fail, so additional checks are required.
* Split glyph runs earlyOleg Oshmyan2020-10-191-1/+0
| | | | | | | | | | | | | | | | | | The "bitmap runs" that are currently used only when combining bitmaps are also relevant at other stages, mainly for VSFilter compatibility. They are not currently used because this information is not available until bitmap combining (except during shaping, which has its own "shape runs" that duplicate a good chunk of the "bitmap run" logic). Move some code around to compute run boundaries as early as possible. This lays the foundation for future commits that will make use this information in more places where it can simplify code or improve VSFilter compatibility. For VSFilter compatibility, rather than break runs immediately upon line breaks, break runs after line-leading whitespace, even in the first line. These runs correspond to VSFilter's CWord instances.
* Fix Scroll effects with rectangle \clip/\iclipOleg Oshmyan2020-10-181-2/+2
|
* Disable collision detection for Banner effect eventsOleg Oshmyan2020-10-181-0/+1
| | | | This matches VSFilter.
* Don't special-case Scroll effect with bigger coordinate = 0Oleg Oshmyan2020-10-181-2/+0
| | | | VSFilter does no such thing.
* Support Banner/Scroll effects with \pos/\moveOleg Oshmyan2020-10-181-6/+6
|
* Default Banner effect to right-to-leftOleg Oshmyan2020-10-181-3/+3
| | | | That's what VSFilter does.
* Support line breaks with Banner effectOleg Oshmyan2020-10-181-0/+1
| | | | | | | | | Make Banner default to \q2, but allow explicit line breaks and \q overrides. Justify the lines according to \a etc., and wrap lines as usual if \q is overridden, but make sure to keep the left/right edge of the whole event flush with the edge of the screen at the event's start time as required by Banner. This is what VSFilter does.
* mult_alpha: round the productOleg Oshmyan2020-10-181-1/+1
| | | | | | For what it's worth, VSFilter does this too, and our mult_alpha now gives the same results as the corresponding code in VSFilter for all possible inputs.
* Fix mult_alpha of large argumentOleg Oshmyan2020-10-181-2/+3
| | | | | | | | | | | | This function is passed alpha values from \fade, which are restricted to nonnegative values but have no upper limit. Given a large value, the subtraction and the multiply-divide could wrap around or even overflow (in case of integer promotion to signed int). Overflow is undesirable due to undefined behavior, and wraparound is also undesirable due to VSFilter compatibility. Rewrite the expression as a simpler equivalent that does not need to deal with negative numbers and works correctly regardless of integer promotion.
* parse_tag: handle timestamps w/o overflow & like VSFilterOleg Oshmyan2020-10-181-32/+31
| | | | | | | | | | | | | | | | | | | | | | | | | | | We generally support 64-bit timestamps. However, for better VSFilter compatibility and to avoid overflow, when handling \t, \move and \fad(e), parse timestamps with 32 bits like VSFilter does and perform wraparound 32-bit subtraction. To match VSFilter results when the parsed arguments are large but the video/event durations are short, clip the current frame time and event duration to 32 bits during this. Note: we generally handle large event and video timestamps differently from VSFilter (which uses 32 bits throughout) regardless of this commit, so in a sense, this commit actually breaks our saner rendering of long- duration events while it remains VSFilter-incompatible. (It does not break large event/video timestamps per se as long as each individual event's duration fits in 31 bits.) The gain is VSFilter-compatible rendering when all video/event timestamps fit in 31 bits but override tags have values that don't (or whose differences don't) fit. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=542. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=602. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=624. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=2721. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=3880. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11632. Closes https://github.com/libass/libass/pull/337.
* Fix integer overflow while parsing \fad(arg, large negative number)Oleg Oshmyan2020-10-181-1/+6
| | | | | | | | | | | If t3 is initially negative, it should be set to a value larger than the duration of the event. This triggers the `now < t3` branch in interpolate_alpha (if none of the earlier branches are taken). The same effect can be achieved by setting t3 to the duration itself. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=531. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=3905. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=11736.
* Factor out & improve UB-less double -> int32_t conversionOleg Oshmyan2020-10-181-7/+12
| | | | | | | | | The value may be bigger than INT32_MAX as long as its *integral part* isn't. Furthermore, make sure to avoid UB on odd platforms where int32_t limits aren't exactly representable in double, as this isn't too hard. Do assume that they're representable at all though. We won't necessarily match x86 values on such platforms, but we will avoid the undefined behavior.
* ass_parse: avoid UB and match vsfilter on negative-accel color animationrcombs2020-09-191-4/+25
| | | | | | | | | | | | | | | | | | | | Providing a negative acceleration to \t could result in undefined behavior due to overflow in float->int conversion. This codifies the same behavior we currently have on x86, which matches vsfilter's, without actually invoking UB. Additionally, vsfilter color animations work subtly differently than ours did. We used to lerp each individual color component's byte value, while vsfilter performs the lerp on the component _in place within a larger int_. This didn't result in major issues for most cases, but could probably result in subtle rounding errors, and gave vastly different results for \t with negative acceleration. Negative \t acceleration is probably completely useless, but our behavior was wacky in a different way from vsfilter's, and I'd rather have portable wackiness than libass-specific wackiness. It might still be possible to invoke UB in negative-acceleration \t using tags other than colors; we should fix those cases as well.
* Skip fully parsing \t if there is no backslashOleg Oshmyan2020-09-191-2/+10
| | | | | | | | | | | | | | | | | | | | | | | | | | The assertion in commit 66cef6774386d558e1e39096db926d677dad6882 fires on the following ASS code: \t(\t(foobar,) (where "foobar" is any nonblank sequence that does not contain a backslash) The outer \t is parsed as having a single argument "\t(foobar," including the comma. The inner \t is parsed as having a single argument "foobar" *excluding* the comma. As a result, in the inner parse_tags, args[cnt].end == end - 1 && nested, and the assert fires. This is because arguments that contain backslashes are parsed differently from arguments that do not. But if the argument to \t contains no backslashes, why bother parsing it at all? It clearly has no override tags and affects nothing. Rather than try to make the assert more clever (and more convoluted), this commit skips parsing the last \t argument if it has no backslash. The assert is now valid. This probably does not significantly affect parsing speed in either direction. Fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=25796.
* Assert finite parse_tags recursionOleg Oshmyan2020-09-081-0/+1
| | | | | | | | | | | | | | | | | | Before commit 6835731c2fe4164a0c50bc91d12c43b2a2b4e799, parse_tags used to recurse for each nested \t(). The depth of this recursion was not limited, and each \t in \t(\t(\t(... added another level. This could lead to stack overflow. Since that commit, parse_tags still recurses, but at most once: it is called with nested=false at the top level and recurses with nested=true for the outermost \t() (except rare cases in which even this one level of recursion is avoided). Parsing stops at the closing ) both for the outermost \t() and for any inner \t() nested inside it, so the inner recursive call cannot recurse further. This was not immediately obvious when reading the code, and therefore it was not obvious that stack overflow is avoided. Make it so by adding an assertion.
* ass_parse: improve performance of tag name comparisonsrcombs2020-08-301-5/+7
| | | | | | | | | | Yes, this actually makes a major difference on some tag-heavy scripts. This lets the whole function get inlined, rather than making a call out to the libc's strcmp implementation. The libc's version is likely much faster on longer strings, but we're only ever comparing against strings that are a few characters long, so that doesn't really matter. It also avoids making an extra pass for the strlen.
* parse: fix setting font size when no font is loaded; fixes #365rcombs2020-01-061-10/+1
|
* parse_tags: fix case where t==t1==t2Rodger Combs2019-11-071-1/+1
| | | | | This previously gave the pre-transition value; VSFilter's behavior is to give the post-transition value.
* cache: construct cache values only from corresponding keysDr.Smile2019-05-191-4/+1
| | | | | | | | | | | | | | | | | | | | This commit forces construction of cache values using only data available in its companion keys. That ensures logical correctness: keys are guaranteed to have all the necessary data, and prevents accidental collisions. Most fixes of cache logic correspond to minor problem when rendering is done with double parameter but cache key stores its approximate fixed-point representation. The only serious problem is missing scale of clip drawing. Also this commit removes unused scale parameters from glyph metrics cache key. Due to missing scale clip shapes that differed only in scale treated by cache system as identical. That can lead to incorrect reuse of cached bitmap of different scale instead of correct one. The only hack left is in glyph metrics cache with its unicode >= VERTICAL_LOWER_BOUND check.
* drawing: separate drawing text reading from outline constructionDr.Smile2019-05-191-20/+11
| | | | Purpose of this commit is to simplify logic behind drawing handling.
* parse_tags: handle argumentless \t inside \t() like VSFilterOleg Oshmyan2018-01-081-5/+10
| | | | | | | | | | | | | | | | | | | | | | | | | | \t with no parantheses inside \t() resets the animation parameters of the \t() for subsequent tags, so they are animated as if the \t() was the single-argument version regardless of the actual number of arguments the \t() has. Equivalently, you could say parentheses are implied for \t inside \t(). For example, \t(20,60,\frx0\t\fry0\frz0) animates \frx from 20 to 60 ms and animates \fry and \frz for the whole duration of the line, just like \t(20,60,\frx0)\t(\fry0\frz0) or \t(20,60,\frx0\t(\fry0\frz0)). Technically, VSFilter simply resets the animation parameters for any \t it encounters but parses the embedded tags only if the \t has the right number of arguments. However, top-level animation parameters don't matter because top-level tags are not animated, while any nested \t that has parentheses terminates the containing \t because they share the closing parenthesis, so the fact that a nested \t with empty parentheses or with at least four arguments changes the animation parameters also doesn't matter because the containing \t immediately ends and the changed parameters have nothing to apply to. Thus the only situation where this has a visible effect is a nested \t without parentheses. Closes https://github.com/libass/libass/pull/296.
* parse_tags: don't recurse for nested \t()Oleg Oshmyan2018-01-081-1/+11
| | | | | | | | | | | | | This fixes https://bugs.chromium.org/p/oss-fuzz/issues/detail?id=4892 (stack overflow on deeply nested \t()). This is possible because parentheses do not nest and the first ')' terminates the whole tag. Thus something like \t(\t(\t(\t(\t() can be read in a simple loop with no recursion required. Recursion is also not required if the ')' is missing entirely and the outermost \t(... never ends. See https://github.com/libass/libass/pull/296 for more backstory.
* Move parse_tag loop into parse_tag itself, now called parse_tagsOleg Oshmyan2018-01-051-586/+586
| | | | This commit is mostly transparent to `git blame -w`.
* Replace FreeType types with libass native typesDr.Smile2017-09-171-4/+3
| |