diff --git a/src/main/python/ttconv/imsc/elements.py b/src/main/python/ttconv/imsc/elements.py index acc48207..18a51d6a 100644 --- a/src/main/python/ttconv/imsc/elements.py +++ b/src/main/python/ttconv/imsc/elements.py @@ -610,9 +610,9 @@ def from_xml( model_prop, model_value = prop.to_model(style_ctx, xml_elem) style_ctx.styles[model_prop] = model_value - except ValueError: + except ValueError as e: - LOGGER.error("Error reading style property: %s", prop.__name__) + LOGGER.error("Error reading style property %s with %s", prop.__name__, str(e)) # merge nested style attributes if the parent is a region element @@ -683,9 +683,9 @@ def from_xml( initial_ctx.doc.put_initial_value(model_prop, model_value) - except (ValueError, TypeError): + except (ValueError, TypeError) as e: - LOGGER.error("Error reading style property: %s", prop.__name__) + LOGGER.error("Error reading style property %s with %s", prop.__name__, str(e)) return initial_ctx @@ -767,9 +767,9 @@ def process_specified_styling(self, xml_elem): self.model_element.set_style(model_prop, model_value) - except ValueError: + except ValueError as e: - LOGGER.error("Error reading style property: %s", prop.__name__) + LOGGER.error("Error reading style property %s with %s", prop.__name__, str(e)) def process_set_style_properties(self, parent_ctx: ContentElement.ParsingContext, xml_elem): '''Processes style properties on `` element @@ -796,8 +796,8 @@ def process_set_style_properties(self, parent_ctx: ContentElement.ParsingContext ) ) break - except ValueError: - LOGGER.error("Error reading style property: %s", prop.__name__) + except ValueError as e: + LOGGER.error("Error reading style property %s with %s", prop.__name__, str(e)) def process_lang_attribute(self, parent_ctx: TTMLElement.ParsingContext, xml_elem): super().process_lang_attribute(parent_ctx, xml_elem) diff --git a/src/main/python/ttconv/imsc/style_properties.py b/src/main/python/ttconv/imsc/style_properties.py index d6045305..5d4ecee6 100644 --- a/src/main/python/ttconv/imsc/style_properties.py +++ b/src/main/python/ttconv/imsc/style_properties.py @@ -317,6 +317,22 @@ def from_model(cls, xml_element, model_value: styles.FontStyleType): xml_element.set(f"{{{cls.ns}}}{cls.local_name}", model_value.value) + class FontVariant(StyleProperty): + '''Corresponds to tts:fontVariant.''' + + ns = xml_ns.TTS + local_name = "fontVariant" + model_prop = styles.StyleProperties.FontVariant + + @classmethod + def extract(cls, context: StyleParsingContext, xml_attrib: str): + return styles.FontVariantType(xml_attrib) + + @classmethod + def from_model(cls, xml_element, model_value: styles.FontVariantType): + xml_element.set(f"{{{cls.ns}}}{cls.local_name}", model_value.value) + + class FontWeight(StyleProperty): '''Corresponds to tts:fontWeight.''' diff --git a/src/main/python/ttconv/isd.py b/src/main/python/ttconv/isd.py index 3a543336..79e86301 100644 --- a/src/main/python/ttconv/isd.py +++ b/src/main/python/ttconv/isd.py @@ -161,7 +161,11 @@ def get_region(self, region_id) -> typing.Optional[ISD.Region]: def iter_regions(self) -> typing.Iterator[ISD.Region]: '''Returns an iterator over regions.''' - return self._regions.values() + return iter(self._regions.values()) + + def __iter__(self) -> typing.Iterator[ISD.Region]: + '''Returns an iterator over the children of the element.''' + return iter(self.iter_regions()) def __len__(self) -> int: '''Returns the number of regions of the ISD.''' @@ -958,6 +962,9 @@ def compute(cls, parent: model.ContentElement, element: model.ContentElement): class FontStyle(StyleProcessor): style_prop = styles.StyleProperties.FontStyle + class FontVariant(StyleProcessor): + style_prop = styles.StyleProperties.FontVariant + class FontWeight(StyleProcessor): style_prop = styles.StyleProperties.FontWeight diff --git a/src/main/python/ttconv/model.py b/src/main/python/ttconv/model.py index 18e28f47..d2f96b27 100644 --- a/src/main/python/ttconv/model.py +++ b/src/main/python/ttconv/model.py @@ -495,6 +495,7 @@ class P(ContentElement): StyleProperties.FontFamily, StyleProperties.FontSize, StyleProperties.FontStyle, + StyleProperties.FontVariant, StyleProperties.FontWeight, StyleProperties.LineHeight, StyleProperties.LinePadding, @@ -525,6 +526,7 @@ class Span(ContentElement): StyleProperties.FontFamily, StyleProperties.FontSize, StyleProperties.FontStyle, + StyleProperties.FontVariant, StyleProperties.FontWeight, StyleProperties.Opacity, StyleProperties.TextCombine, diff --git a/src/main/python/ttconv/style_properties.py b/src/main/python/ttconv/style_properties.py index 58f111fb..3f3fcc76 100644 --- a/src/main/python/ttconv/style_properties.py +++ b/src/main/python/ttconv/style_properties.py @@ -165,6 +165,14 @@ class FontStyleType(Enum): oblique = "oblique" +class FontVariantType(Enum): + '''tts:fontVariant value + ''' + normal = "normal" + superscript = "super" + subscript = "sub" + + class FontWeightType(Enum): '''tts:fontWeight value ''' @@ -610,6 +618,21 @@ def validate(value): return isinstance(value, FontStyleType) + class FontVariant(StyleProperty): + '''Corresponds to tts:fontVariant.''' + + is_inherited = True + is_animatable = True + + @staticmethod + def make_initial_value(): + return FontVariantType.normal + + @staticmethod + def validate(value): + return isinstance(value, FontVariantType) + + class FontWeight(StyleProperty): '''Corresponds to tts:fontWeight.''' @@ -717,8 +740,8 @@ def make_initial_value(): @staticmethod def validate(value: CoordinateType): return isinstance(value, CoordinateType) \ - and value.x.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rw) \ - and value.y.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh) \ + and value.x.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh, LengthType.Units.rw) \ + and value.y.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh, LengthType.Units.rw) \ and value.x.is_non_negative() and value.y.is_non_negative() @@ -771,8 +794,8 @@ def make_initial_value(): @staticmethod def validate(value: PositionType): return isinstance(value, PositionType) \ - and value.h_offset.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rw) \ - and value.v_offset.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh) \ + and value.h_offset.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh, LengthType.Units.rw) \ + and value.v_offset.units in (LengthType.Units.pct, LengthType.Units.px, LengthType.Units.rh, LengthType.Units.rw) \ and value.h_offset.is_non_negative() and value.v_offset.is_non_negative() class RubyAlign(StyleProperty): diff --git a/src/test/python/test_imsc11text_filter.py b/src/test/python/test_imsc11text_filter.py index 78a8289d..d960fc93 100644 --- a/src/test/python/test_imsc11text_filter.py +++ b/src/test/python/test_imsc11text_filter.py @@ -630,6 +630,18 @@ def test_imsc_1_1_test_suite(self): filt = IMSC11TextFilter() filt.process(model) + def test_imsc_1_3_test_suite(self): + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name): + tree = et.parse(os.path.join(root, filename)) + model = imsc_reader.to_model(tree) + self.assertIsNotNone(model) + filt = IMSC11TextFilter() + with self.assertRaises(ValueError): + filt.process(model) if __name__ == "__main__": unittest.main() diff --git a/src/test/python/test_imsc_reader.py b/src/test/python/test_imsc_reader.py index f84188ca..d131faa5 100644 --- a/src/test/python/test_imsc_reader.py +++ b/src/test/python/test_imsc_reader.py @@ -189,6 +189,15 @@ def test_imsc_1_1_test_suite(self): tree = et.parse(os.path.join(root, filename)) self.assertIsNotNone(imsc_reader.to_model(tree)) + def test_imsc_1_3_test_suite(self): + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name): + tree = et.parse(os.path.join(root, filename)) + self.assertIsNotNone(imsc_reader.to_model(tree)) + def test_referential_styling(self): tree = et.parse('src/test/resources/ttml/referential_styling.ttml') doc = imsc_reader.to_model(tree) diff --git a/src/test/python/test_imsc_writer.py b/src/test/python/test_imsc_writer.py index d1a5a5ea..684e5be6 100644 --- a/src/test/python/test_imsc_writer.py +++ b/src/test/python/test_imsc_writer.py @@ -157,6 +157,37 @@ def test_imsc_1_1_test_suite(self): with open(os.path.join(base_path, "tests.json"), "w", encoding="utf8") as fp: json.dump(manifest, fp) + def test_imsc_1_3_test_suite(self): + manifest = [] + base_path = "build/imsc1_3" + + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name), self.assertLogs() as logs: + logging.getLogger().info("*****dummy*****") # dummy log + tree = et.parse(os.path.join(root, filename)) + test_model = imsc_reader.to_model(tree) + tree_from_model = imsc_writer.from_model(test_model) + + test_dir_relative_path = os.path.basename(root) + + test_relative_path = os.path.join(test_dir_relative_path, filename) + + os.makedirs(os.path.join(base_path, "ttml", test_dir_relative_path), exist_ok=True) + + with open(os.path.join(base_path, "ttml", test_relative_path), "wb") as f: + f.write(et.tostring(tree_from_model.getroot(), 'utf-8')) + + manifest.append({"path" : str(test_relative_path).replace('\\', '/')}) + + if len(logs.output) > 1: + self.fail(logs.output) + + with open(os.path.join(base_path, "tests.json"), "w", encoding="utf8") as fp: + json.dump(manifest, fp) + class FromModelBodyWriterTest(unittest.TestCase): diff --git a/src/test/python/test_isd.py b/src/test/python/test_isd.py index 01157e55..a2d83b36 100644 --- a/src/test/python/test_isd.py +++ b/src/test/python/test_isd.py @@ -140,6 +140,11 @@ def setUp(self): t1 = model.Text(self.doc, "hello") span1.push_child(t1) + def test_iter(self): + isd = ISD.from_model(self.doc, 2) + r_ids = sorted(r.get_id() for r in isd) + self.assertSequenceEqual(r_ids, sorted(("r1", "r2"))) + def test_significant_times(self): self.assertSequenceEqual(ISD.significant_times(self.doc), sorted((0, 2, 3, 9, 1, 10, 4))) @@ -273,6 +278,23 @@ def test_imsc_1_1_test_suite(self): self.assertIsNotNone(isd) if len(logs.output) > 1: self.fail(logs.output) + + def test_imsc_1_3_test_suite(self): + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name), self.assertLogs() as logs: + logging.getLogger().info("*****dummy*****") # dummy log + tree = et.parse(os.path.join(root, filename)) + m = imsc_reader.to_model(tree) + self.assertIsNotNone(m) + sig_times = ISD.significant_times(m) + for t in sig_times: + isd = ISD.from_model(m, t) + self.assertIsNotNone(isd) + if len(logs.output) > 1: + self.fail(logs.output) class ComputeStyleTest(unittest.TestCase): diff --git a/src/test/python/test_model_style_properties.py b/src/test/python/test_model_style_properties.py index dbef7274..0163a279 100644 --- a/src/test/python/test_model_style_properties.py +++ b/src/test/python/test_model_style_properties.py @@ -27,12 +27,53 @@ # pylint: disable=R0201,C0115,C0116 +from fractions import Fraction import unittest from ttconv import model -from ttconv.style_properties import ExtentType, LengthType, StyleProperties, TextShadowType +from ttconv.isd import ISD +from ttconv.style_properties import ExtentType, FontVariantType, LengthType, NamedColors, StyleProperties, TextShadowType class TestModelStyleProperties(unittest.TestCase): + def setUp(self): + self.doc = model.ContentDocument() + + self.r1 = model.Region("r1", self.doc) + self.doc.put_region(self.r1) + + self.b = model.Body(self.doc) + self.doc.set_body(self.b) + + self.div1 = model.Div(self.doc) + self.div1.set_region(self.r1) + self.b.push_child(self.div1) + + self.p1 = model.P(self.doc) + self.div1.push_child(self.p1) + + self.span1 = model.Span(self.doc) + self.p1.push_child(self.span1) + + self.text1 = model.Text(self.doc, "span1") + self.span1.push_child(self.text1) + + def test_fontVariant(self): + self.b.set_style(StyleProperties.FontVariant, FontVariantType.subscript) + self.assertFalse(self.b.is_style_applicable(StyleProperties.FontVariant)) + + a = model.DiscreteAnimationStep(StyleProperties.FontVariant, 1, None, FontVariantType.superscript) + self.p1.add_animation_step(a) + + isd = ISD.from_model(self.doc, 0) + r = list(isd)[0] + self.assertIsNone(r[0][0].get_style(StyleProperties.FontVariant)) + self.assertEqual(r[0][0][0][0].get_style(StyleProperties.FontVariant), FontVariantType.subscript) + + isd = ISD.from_model(self.doc, 1) + r = list(isd)[0] + self.assertIsNone(r[0][0].get_style(StyleProperties.FontVariant)) + self.assertEqual(r[0][0][0][0].get_style(StyleProperties.FontVariant), FontVariantType.superscript) + def test_make_initial(self): for style in StyleProperties.ALL: with self.subTest(style.__name__): diff --git a/src/test/python/test_srt_writer.py b/src/test/python/test_srt_writer.py index 2210ca10..ba836ba5 100644 --- a/src/test/python/test_srt_writer.py +++ b/src/test/python/test_srt_writer.py @@ -155,6 +155,20 @@ def test_imsc_1_2_test_suite(self): srt_from_model = srt_writer.from_model(test_model) self._check_output_srt(test_model, srt_from_model, path) + @unittest.skip("IMSC 1.3 is not supported") + def test_imsc_1_3_test_suite(self): + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name): + path = os.path.join(root, filename) + tree = et.parse(path) + test_model = imsc_reader.to_model(tree) + srt_from_model = srt_writer.from_model(test_model) + self._check_output_srt(test_model, srt_from_model, path) + + # # Utility functions # diff --git a/src/test/python/test_vtt_writer.py b/src/test/python/test_vtt_writer.py index 8aec6d67..b6e4609b 100644 --- a/src/test/python/test_vtt_writer.py +++ b/src/test/python/test_vtt_writer.py @@ -312,6 +312,18 @@ def test_imsc_1_2_test_suite(self): vtt_from_model =vtt_writer.from_model(test_model, None) self._check_output_vtt(test_model, vtt_from_model, path) + @unittest.skip("IMSC 1.3 is not supported") + def test_imsc_1_3_test_suite(self): + for root, _subdirs, files in os.walk("src/test/resources/ttml/imsc-tests/imsc1_3/ttml"): + for filename in files: + (name, ext) = os.path.splitext(filename) + if ext == ".ttml": + with self.subTest(name): + path = os.path.join(root, filename) + tree = et.parse(path) + test_model = imsc_reader.to_model(tree) + vtt_from_model =vtt_writer.from_model(test_model, None) + self._check_output_vtt(test_model, vtt_from_model, path) # # Utility functions # diff --git a/src/test/resources/isd-references/imsc1_1_ttml_linePadding_linepadding002.txt b/src/test/resources/isd-references/imsc1_1_ttml_linePadding_linepadding002.txt new file mode 100644 index 00000000..dd2f7e52 --- /dev/null +++ b/src/test/resources/isd-references/imsc1_1_ttml_linePadding_linepadding002.txt @@ -0,0 +1,2 @@ +0.0 + +1.0 - diff --git a/src/test/resources/isd-references/imsc1_1_ttml_textCombine_textCombine002.txt b/src/test/resources/isd-references/imsc1_1_ttml_textCombine_textCombine002.txt new file mode 100644 index 00000000..dd2f7e52 --- /dev/null +++ b/src/test/resources/isd-references/imsc1_1_ttml_textCombine_textCombine002.txt @@ -0,0 +1,2 @@ +0.0 + +1.0 - diff --git a/src/test/resources/isd-references/imsc1_3_ttml_fontVariant_fontVariant001.txt b/src/test/resources/isd-references/imsc1_3_ttml_fontVariant_fontVariant001.txt new file mode 100644 index 00000000..8491b2fb --- /dev/null +++ b/src/test/resources/isd-references/imsc1_3_ttml_fontVariant_fontVariant001.txt @@ -0,0 +1 @@ +0.0 + diff --git a/src/test/resources/isd-references/imsc1_ttml_fillLineGap_FillLineGap005.txt b/src/test/resources/isd-references/imsc1_ttml_fillLineGap_FillLineGap005.txt new file mode 100644 index 00000000..79aca0e5 --- /dev/null +++ b/src/test/resources/isd-references/imsc1_ttml_fillLineGap_FillLineGap005.txt @@ -0,0 +1,2 @@ +0.0 + +30.0 - diff --git a/src/test/resources/ttml/imsc-tests b/src/test/resources/ttml/imsc-tests index 28cefda2..3de51082 160000 --- a/src/test/resources/ttml/imsc-tests +++ b/src/test/resources/ttml/imsc-tests @@ -1 +1 @@ -Subproject commit 28cefda25c51a31df3833deb6b74bdd5add8b14f +Subproject commit 3de51082127b58a8cd5cc3b6f2ec1b849f0242ab