Structured Subtitle Format 1.0 (boring name, need a better one) ------------------------------ The encoding must be utf-8/16le/16be with the BOM at the beginning. Parsing is prefered to be stream oriented, which means: - new-line characters in text do not differ from the rest of the white-space characters - forward references are not allowed Comments -------- // a comment, ends at the end of the line /* this is a comment, too */ Syntax ------ Fortunatelly, there is only one general way to define something. The elements are broken into lines for better understanding, but they can be compressed into one single line as well, whichever suits your preference. The term "type" could be read as "attribute" or "member of a structure" too. [!] [type[.type[..]]] [#name] [: | =] [refs | quoted string | num | bool | raw] ; As you can see nearly everything is optional, even the terminating semi-colon is not required when a closing bracket ends the definition anyway. However, either type or #name must be given, otherwise there would be no meaning of the expression. "!": - gives the definition high priority, normal priority types of the same kind cannot override it: #a {!t: 123;}; #b {t: 234;}; #c a b; // t of c equals to 123 - works on references too: !#a {t: 123;}; #b {t: 234;}; #ab a b; #c ab; // t of c still equals to 123 "type" and "name": - alphanumeric or underscore characters only, without spaces - cannot start with a number - case-sensitive "type": - type is optional but can be inherited through referencing other names which happen to have a type already color#c1 {a: 0x80;}; #c2: c1; // c2 also has the type of color now #c3: c2; // c3 too - if there is a type already additional references cannot change it - when mixing different types (not recommended) the first one decides - there is one special type which have an important but a limited use: @ (see 'subtitle' for an example usage) - it is not parsed for references or nested definitions - the content of the following block {...} is saved as-is for the application - cannot be global or named and therefore referenced - { and } have to be escaped with \ inside the block (other block specific characters may as well) - type.type.type.... is equal to writing: type {type {type {...};};}; "name": - every name is globally visible - redefining a name is forbidden, unless it was predefined by the application - using the type as the name (type#type) can change the type's default values - however, the scope is important: subtitle#subtitle {style.font.size: 20;}; style#style {font.size: 30;}; style#s1 {font.face: "Arial";}; style#s2 : s1 {font.color: red;}; subtitle#a {style: s2 {font.weight: "normal";};}; Here font.size equals to 20, because it inherits from subtitle#subtitle.style instead of the global style#style, additionally it gets the properties of s2-s1 and the inline def. If it also inherited properties from style#style through s2-s1 indirectly, there would be two default base definitions and the order of overriding eachother would not be clear. subtitle#a.style <- subtitle#subtitle.style <- s2 <- s1 <-NOT- style#style <- {font.weight: "normal";} "refs": - combination of any names or nested definitions separated by spaces - forward references are not allowed - referencing a value type directly is not allowed: (TODO: this may change) OK: color#c1: {a: 12;}; color#c2: c1; BAD: #twelve: 12; color#c2: {a: twelve;}; - 'name' must have been defined by a parent node OK: #c1: {a: 12;}; style#s1 {color: c1;}; style#s2 {color: c1;}; BAD: style#s1 {color#c1: {a: 12;};}; style#s2 {color: c1;}; // c1 cannot be accessed here "quoted string" or 'quoted string': - \ escapes characters, including " and ' - cannot contain any new-line characters "num": - decimal, hexadecimal (prefixed with: 0x), float [+ unit (optional, see 'time' below for an example)] - there are numbers with restricted range or special formatting: - degrees: mod 360 - percent: must be between 0 and 1 - time: [+] [:[:[.]]] | h | m | s | ms "bool": - "true" | "false" | "on" | "off" | "yes" | "no" | 1 | 0 - unrecognizable values will result in unpredictable behaviour, since there can't be a default fallback value Recognized types and attributes ------------------------------- file { format: ; // identifies the file format ("ssf") version: ; // file format version (1) language: ; // iso6392 title: ; year: ; author: ; }; color { // a, r, g, b: 0 - 255 or 0x00 - 0xff a: ; r: ; g: ; b: ; }; point { x: ; y: ; }; size { cx: ; cy: ; }; rect { t: ; r: ; b: ; l: ; }; align { // when given in percent, 0 means top/left, 1 bottom/right, 0.5 middle/center v: ["top" | "middle" | "bottom" | ]; h: ["left" | "center" | "right" | ]; }; angle { x: ; y: ; z: ; }; frame { reference: ["video" | "window"]; resolution: ; }; direction { primary: ["right" | "left" | "down" | "up"]; secondary: ["right" | "left" | "down" | "up"]; // must be perpendicular to primary }; placement { clip: ["none" | "frame" | ]; // anything drawn outside this rectangle is clipped, negative or larger than 'resolution' values are not allowed for rect margin: ; // "top" "right" "bottom" "left" are also valid values for the rect members, they refer to the values of the "frame" rect (0, 0, frame.resolution.cx, frame.resolution.cy) align: ; pos: ["auto" | ]; // absolute values, pos.x or pos.y can be animated only when both the source and destination style defined it offset: ; // relative to pos, unlike pos this can be applied to fragments of the text as an override angle: ; // origin of rotation is the alignment point, unless it is overriden org: ["auto" | ]; // override for the origin path: ; // a series of x y coord pairs (at least two points) }; font { face: ; size: ; weight: ["normal" | "bold" | "thin" | ]; color: ; underline: ; strikethrough: ; italic: ; spacing: ; scale: ; kerning: ; }; background { color: ; size: ; type: ["outline" | "enlarge" | "box"]; blur: ; // number of passes // "enlarge" can be computed faster, but because it follows the path of the original outline it is // not rounded and size > 1 can make it look pretty bad if it intersects with itself. }; shadow { color: ; depth: ; angle: ; blur: ; // number of passes }; fill { color: ; width: ; // It cannot be applied to the same text multiple times as an override. // // #k1 {fill.width:1; time {start: +0; stop: +1s;}}; // #k2 {fill.width:1; time {start: 0; stop: 2s;}}; // // OK: // [k1] {Hello} // // BAD: // [k1] {Wo[k2]{r}ld!} // }; time { id: [ | ]; start: