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 | |
45 | namespace WebCore { |
46 | |
47 | WTF_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 |
50 | const static int DEFAULTCAPTIONFONTSIZE = 10; |
51 | |
52 | class TextTrackCueGenericBoxElement final : public VTTCueBox { |
53 | WTF_MAKE_ISO_ALLOCATED_INLINE(TextTrackCueGenericBoxElement); |
54 | public: |
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 | |
62 | private: |
63 | TextTrackCueGenericBoxElement(Document&, VTTCue&); |
64 | }; |
65 | |
66 | TextTrackCueGenericBoxElement::TextTrackCueGenericBoxElement(Document& document, VTTCue& cue) |
67 | : VTTCueBox(document, cue) |
68 | { |
69 | } |
70 | |
71 | void 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 | |
158 | TextTrackCueGeneric::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 | |
165 | Ref<VTTCueBox> TextTrackCueGeneric::createDisplayTree() |
166 | { |
167 | return TextTrackCueGenericBoxElement::create(ownerDocument(), *this); |
168 | } |
169 | |
170 | ExceptionOr<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 | |
178 | ExceptionOr<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 | |
186 | void 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 | |
202 | bool 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 | |
225 | bool 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 | |
239 | bool TextTrackCueGeneric::doesExtendCue(const TextTrackCue& cue) const |
240 | { |
241 | if (!cueContentsMatch(cue)) |
242 | return false; |
243 | |
244 | return VTTCue::doesExtendCue(cue); |
245 | } |
246 | |
247 | bool 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 | |
262 | bool 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 | |
277 | String 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 | |