1/*
2 * Copyright (C) 2013, 2014 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 * 1. Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27
28#if ENABLE(VIDEO_TRACK)
29
30#include "TextTrackCueGeneric.h"
31
32#include "CSSPropertyNames.h"
33#include "CSSStyleDeclaration.h"
34#include "CSSValueKeywords.h"
35#include "HTMLSpanElement.h"
36#include "InbandTextTrackPrivateClient.h"
37#include "Logging.h"
38#include "RenderObject.h"
39#include "ScriptExecutionContext.h"
40#include "StyleProperties.h"
41#include "TextTrackCue.h"
42#include <wtf/IsoMallocInlines.h>
43#include <wtf/MathExtras.h>
44
45namespace WebCore {
46
47WTF_MAKE_ISO_ALLOCATED_IMPL(TextTrackCueGeneric);
48
49// This default value must be the same as the one specified in mediaControlsApple.css for -webkit-media-controls-closed-captions-container
50const static int DEFAULTCAPTIONFONTSIZE = 10;
51
52class TextTrackCueGenericBoxElement final : public VTTCueBox {
53 WTF_MAKE_ISO_ALLOCATED_INLINE(TextTrackCueGenericBoxElement);
54public:
55 static Ref<TextTrackCueGenericBoxElement> create(Document& document, TextTrackCueGeneric& cue)
56 {
57 return adoptRef(*new TextTrackCueGenericBoxElement(document, cue));
58 }
59
60 void applyCSSProperties(const IntSize&) override;
61
62private:
63 TextTrackCueGenericBoxElement(Document&, VTTCue&);
64};
65
66TextTrackCueGenericBoxElement::TextTrackCueGenericBoxElement(Document& document, VTTCue& cue)
67 : VTTCueBox(document, cue)
68{
69}
70
71void TextTrackCueGenericBoxElement::applyCSSProperties(const IntSize& videoSize)
72{
73 RefPtr<TextTrackCueGeneric> cue = static_cast<TextTrackCueGeneric*>(getCue());
74 if (!cue)
75 return;
76
77 setInlineStyleProperty(CSSPropertyPosition, CSSValueAbsolute);
78 setInlineStyleProperty(CSSPropertyUnicodeBidi, CSSValuePlaintext);
79
80 Ref<HTMLSpanElement> cueElement = cue->element();
81
82 double textPosition = cue->calculateComputedTextPosition();
83
84 CSSValueID alignment = cue->getCSSAlignment();
85 float size = static_cast<float>(cue->getCSSSize());
86 if (cue->useDefaultPosition()) {
87 setInlineStyleProperty(CSSPropertyBottom, 0, CSSPrimitiveValue::CSS_PX);
88 setInlineStyleProperty(CSSPropertyMarginBottom, 1.0, CSSPrimitiveValue::CSS_PERCENTAGE);
89 } else {
90 setInlineStyleProperty(CSSPropertyLeft, static_cast<float>(textPosition), CSSPrimitiveValue::CSS_PERCENTAGE);
91 setInlineStyleProperty(CSSPropertyTop, static_cast<float>(cue->line()), CSSPrimitiveValue::CSS_PERCENTAGE);
92
93 double authorFontSize = videoSize.height() * cue->baseFontSizeRelativeToVideoHeight() / 100.0;
94 if (!authorFontSize)
95 authorFontSize = DEFAULTCAPTIONFONTSIZE;
96
97 if (cue->fontSizeMultiplier())
98 authorFontSize *= cue->fontSizeMultiplier() / 100;
99
100 double multiplier = fontSizeFromCaptionUserPrefs() / authorFontSize;
101 double newCueSize = std::min(size * multiplier, 100.0);
102 if (cue->getWritingDirection() == VTTCue::Horizontal) {
103 setInlineStyleProperty(CSSPropertyWidth, newCueSize, CSSPrimitiveValue::CSS_PERCENTAGE);
104 if ((alignment == CSSValueMiddle || alignment == CSSValueCenter) && multiplier != 1.0)
105 setInlineStyleProperty(CSSPropertyLeft, static_cast<double>(textPosition - (newCueSize - cue->getCSSSize()) / 2), CSSPrimitiveValue::CSS_PERCENTAGE);
106 } else {
107 setInlineStyleProperty(CSSPropertyHeight, newCueSize, CSSPrimitiveValue::CSS_PERCENTAGE);
108 if ((alignment == CSSValueMiddle || alignment == CSSValueCenter) && multiplier != 1.0)
109 setInlineStyleProperty(CSSPropertyTop, static_cast<double>(cue->line() - (newCueSize - cue->getCSSSize()) / 2), CSSPrimitiveValue::CSS_PERCENTAGE);
110 }
111 }
112
113 double maxSize = 100.0;
114
115 if (alignment == CSSValueEnd || alignment == CSSValueRight)
116 maxSize = textPosition;
117 else if (alignment == CSSValueStart || alignment == CSSValueLeft)
118 maxSize = 100.0 - textPosition;
119
120 if (cue->getWritingDirection() == VTTCue::Horizontal) {
121 setInlineStyleProperty(CSSPropertyMinWidth, "min-content");
122 setInlineStyleProperty(CSSPropertyMaxWidth, maxSize, CSSPrimitiveValue::CSS_PERCENTAGE);
123 } else {
124 setInlineStyleProperty(CSSPropertyMinHeight, "min-content");
125 setInlineStyleProperty(CSSPropertyMaxHeight, maxSize, CSSPrimitiveValue::CSS_PERCENTAGE);
126 }
127
128 if (cue->foregroundColor().isValid())
129 cueElement->setInlineStyleProperty(CSSPropertyColor, cue->foregroundColor().serialized());
130 if (cue->highlightColor().isValid())
131 cueElement->setInlineStyleProperty(CSSPropertyBackgroundColor, cue->highlightColor().serialized());
132
133 if (cue->getWritingDirection() == VTTCue::Horizontal)
134 setInlineStyleProperty(CSSPropertyHeight, CSSValueAuto);
135 else
136 setInlineStyleProperty(CSSPropertyWidth, CSSValueAuto);
137
138 if (cue->baseFontSizeRelativeToVideoHeight())
139 cue->setFontSize(cue->baseFontSizeRelativeToVideoHeight(), videoSize, false);
140
141 if (cue->getAlignment() == VTTCue::Center)
142 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueCenter);
143 else if (cue->getAlignment() == VTTCue::End)
144 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueEnd);
145 else
146 setInlineStyleProperty(CSSPropertyTextAlign, CSSValueStart);
147
148 if (cue->backgroundColor().isValid())
149 setInlineStyleProperty(CSSPropertyBackgroundColor, cue->backgroundColor().serialized());
150 setInlineStyleProperty(CSSPropertyWritingMode, cue->getCSSWritingMode(), false);
151 setInlineStyleProperty(CSSPropertyWhiteSpace, CSSValuePreWrap);
152
153 // Make sure shadow or stroke is not clipped.
154 setInlineStyleProperty(CSSPropertyOverflow, CSSValueVisible);
155 cueElement->setInlineStyleProperty(CSSPropertyOverflow, CSSValueVisible);
156}
157
158TextTrackCueGeneric::TextTrackCueGeneric(ScriptExecutionContext& context, const MediaTime& start, const MediaTime& end, const String& content)
159 : VTTCue(context, start, end, content)
160 , m_baseFontSizeRelativeToVideoHeight(0)
161 , m_fontSizeMultiplier(0)
162{
163}
164
165Ref<VTTCueBox> TextTrackCueGeneric::createDisplayTree()
166{
167 return TextTrackCueGenericBoxElement::create(ownerDocument(), *this);
168}
169
170ExceptionOr<void> TextTrackCueGeneric::setLine(double line)
171{
172 auto result = VTTCue::setLine(line);
173 if (!result.hasException())
174 m_useDefaultPosition = false;
175 return result;
176}
177
178ExceptionOr<void> TextTrackCueGeneric::setPosition(const LineAndPositionSetting& position)
179{
180 auto result = VTTCue::setPosition(position);
181 if (!result.hasException())
182 m_useDefaultPosition = false;
183 return result;
184}
185
186void TextTrackCueGeneric::setFontSize(int fontSize, const IntSize& videoSize, bool important)
187{
188 if (!hasDisplayTree() || !fontSize)
189 return;
190
191 if (important || !baseFontSizeRelativeToVideoHeight()) {
192 VTTCue::setFontSize(fontSize, videoSize, important);
193 return;
194 }
195
196 double size = videoSize.height() * baseFontSizeRelativeToVideoHeight() / 100;
197 if (fontSizeMultiplier())
198 size *= fontSizeMultiplier() / 100;
199 displayTreeInternal().setInlineStyleProperty(CSSPropertyFontSize, lround(size), CSSPrimitiveValue::CSS_PX);
200}
201
202bool TextTrackCueGeneric::cueContentsMatch(const TextTrackCue& cue) const
203{
204 // Do call the parent class cueContentsMatch here, because we want to confirm
205 // the content of the two cues are identical (even though the types are not the same).
206 if (!VTTCue::cueContentsMatch(cue))
207 return false;
208
209 const TextTrackCueGeneric* other = static_cast<const TextTrackCueGeneric*>(&cue);
210
211 if (m_baseFontSizeRelativeToVideoHeight != other->baseFontSizeRelativeToVideoHeight())
212 return false;
213 if (m_fontSizeMultiplier != other->fontSizeMultiplier())
214 return false;
215 if (m_fontName != other->fontName())
216 return false;
217 if (m_foregroundColor != other->foregroundColor())
218 return false;
219 if (m_backgroundColor != other->backgroundColor())
220 return false;
221
222 return true;
223}
224
225bool TextTrackCueGeneric::isEqual(const TextTrackCue& cue, TextTrackCue::CueMatchRules match) const
226{
227 // Do not call the parent class isEqual here, because we are not cueType() == VTTCue,
228 // and will fail that equality test.
229 if (!TextTrackCue::isEqual(cue, match))
230 return false;
231
232 if (cue.cueType() != TextTrackCue::Generic)
233 return false;
234
235 return cueContentsMatch(cue);
236}
237
238
239bool TextTrackCueGeneric::doesExtendCue(const TextTrackCue& cue) const
240{
241 if (!cueContentsMatch(cue))
242 return false;
243
244 return VTTCue::doesExtendCue(cue);
245}
246
247bool TextTrackCueGeneric::isOrderedBefore(const TextTrackCue* that) const
248{
249 if (VTTCue::isOrderedBefore(that))
250 return true;
251
252 if (that->cueType() == Generic && startTime() == that->startTime() && endTime() == that->endTime()) {
253 // Further order generic cues by their calculated line value.
254 std::pair<double, double> thisPosition = getPositionCoordinates();
255 std::pair<double, double> thatPosition = toVTTCue(that)->getPositionCoordinates();
256 return thisPosition.second > thatPosition.second || (thisPosition.second == thatPosition.second && thisPosition.first < thatPosition.first);
257 }
258
259 return false;
260}
261
262bool TextTrackCueGeneric::isPositionedAbove(const TextTrackCue* that) const
263{
264 if (that->cueType() == Generic && startTime() == that->startTime() && endTime() == that->endTime()) {
265 // Further order generic cues by their calculated line value.
266 std::pair<double, double> thisPosition = getPositionCoordinates();
267 std::pair<double, double> thatPosition = toVTTCue(that)->getPositionCoordinates();
268 return thisPosition.second > thatPosition.second || (thisPosition.second == thatPosition.second && thisPosition.first < thatPosition.first);
269 }
270
271 if (that->cueType() == Generic)
272 return startTime() > that->startTime();
273
274 return VTTCue::isOrderedBefore(that);
275}
276
277String TextTrackCueGeneric::toJSONString() const
278{
279 auto object = JSON::Object::create();
280
281 toJSON(object.get());
282
283 if (m_foregroundColor.isValid())
284 object->setString("foregroundColor"_s, m_foregroundColor.serialized());
285 if (m_backgroundColor.isValid())
286 object->setString("backgroundColor"_s, m_backgroundColor.serialized());
287 if (m_highlightColor.isValid())
288 object->setString("highlightColor"_s, m_highlightColor.serialized());
289 if (m_baseFontSizeRelativeToVideoHeight)
290 object->setDouble("relativeFontSize"_s, m_baseFontSizeRelativeToVideoHeight);
291 if (m_fontSizeMultiplier)
292 object->setDouble("fontSizeMultiplier"_s, m_fontSizeMultiplier);
293 if (!m_fontName.isEmpty())
294 object->setString("font"_s, m_fontName);
295
296 return object->toJSONString();
297}
298
299} // namespace WebCore
300
301#endif
302