2 * Copyright (C) 2009 Apple Inc.
3 * Copyright (C) 2009 Google Inc.
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
9 * 1. Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * 2. Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
15 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
16 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
18 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
19 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
20 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
22 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
23 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
25 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 #include "core/paint/MediaControlsPainter.h"
31 #include "bindings/core/v8/ExceptionStatePlaceholder.h"
32 #include "core/html/HTMLMediaElement.h"
33 #include "core/html/TimeRanges.h"
34 #include "core/html/shadow/MediaControlElementTypes.h"
35 #include "core/paint/PaintInfo.h"
36 #include "core/style/ComputedStyle.h"
37 #include "platform/graphics/Gradient.h"
38 #include "platform/graphics/GraphicsContext.h"
42 static double kCurrentTimeBufferedDelta
= 1.0;
44 typedef WTF::HashMap
<const char*, Image
*> MediaControlImageMap
;
45 static MediaControlImageMap
* gMediaControlImageMap
= 0;
47 // Current UI slider thumbs sizes.
48 static const int mediaSliderThumbWidth
= 32;
49 static const int mediaSliderThumbHeight
= 24;
50 static const int mediaVolumeSliderThumbHeight
= 24;
51 static const int mediaVolumeSliderThumbWidth
= 24;
53 // New UI slider thumb sizes, shard between time and volume.
54 static const int mediaSliderThumbTouchWidthNew
= 36; // Touch zone size.
55 static const int mediaSliderThumbTouchHeightNew
= 48;
56 static const int mediaSliderThumbPaintWidthNew
= 12; // Painted area.
57 static const int mediaSliderThumbPaintHeightNew
= 12;
59 // New UI overlay play button size.
60 static const int mediaOverlayPlayButtonWidthNew
= 48;
61 static const int mediaOverlayPlayButtonHeightNew
= 48;
63 // Alpha for disabled elements.
64 static const float kDisabledAlpha
= 0.4;
66 static Image
* platformResource(const char* name
)
68 if (!gMediaControlImageMap
)
69 gMediaControlImageMap
= new MediaControlImageMap();
70 if (Image
* image
= gMediaControlImageMap
->get(name
))
72 if (Image
* image
= Image::loadPlatformResource(name
).leakRef()) {
73 gMediaControlImageMap
->set(name
, image
);
80 static Image
* platformResource(const char* currentName
, const char* newName
)
82 // Return currentName or newName based on current or new playback.
83 return platformResource(RuntimeEnabledFeatures::newMediaPlaybackUiEnabled() ? newName
: currentName
);
86 static bool hasSource(const HTMLMediaElement
* mediaElement
)
88 return mediaElement
->networkState() != HTMLMediaElement::NETWORK_EMPTY
89 && mediaElement
->networkState() != HTMLMediaElement::NETWORK_NO_SOURCE
;
92 static bool paintMediaButton(GraphicsContext
* context
, const IntRect
& rect
, Image
* image
, bool isEnabled
= true)
94 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
95 isEnabled
= true; // New UI only.
98 context
->beginLayer(kDisabledAlpha
);
100 context
->drawImage(image
, rect
);
108 bool MediaControlsPainter::paintMediaMuteButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
110 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
114 // The new UI uses "muted" and "not muted" only.
115 static Image
* soundLevel3
= platformResource("mediaplayerSoundLevel3",
116 "mediaplayerSoundLevel3New");
117 static Image
* soundLevel2
= platformResource("mediaplayerSoundLevel2",
118 "mediaplayerSoundLevel3New");
119 static Image
* soundLevel1
= platformResource("mediaplayerSoundLevel1",
120 "mediaplayerSoundLevel3New");
121 static Image
* soundLevel0
= platformResource("mediaplayerSoundLevel0",
122 "mediaplayerSoundLevel0New");
123 static Image
* soundDisabled
= platformResource("mediaplayerSoundDisabled",
124 "mediaplayerSoundLevel0New");
126 if (!hasSource(mediaElement
) || !mediaElement
->hasAudio())
127 return paintMediaButton(paintInfo
.context
, rect
, soundDisabled
, false);
129 if (mediaElement
->muted() || mediaElement
->volume() <= 0)
130 return paintMediaButton(paintInfo
.context
, rect
, soundLevel0
);
132 if (mediaElement
->volume() <= 0.33)
133 return paintMediaButton(paintInfo
.context
, rect
, soundLevel1
);
135 if (mediaElement
->volume() <= 0.66)
136 return paintMediaButton(paintInfo
.context
, rect
, soundLevel2
);
138 return paintMediaButton(paintInfo
.context
, rect
, soundLevel3
);
141 bool MediaControlsPainter::paintMediaPlayButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
143 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
147 static Image
* mediaPlay
= platformResource("mediaplayerPlay", "mediaplayerPlayNew");
148 static Image
* mediaPause
= platformResource("mediaplayerPause", "mediaplayerPauseNew");
149 // For this case, the new UI draws the normal icon, but the entire panel
151 static Image
* mediaPlayDisabled
= platformResource("mediaplayerPlayDisabled", "mediaplayerPlayNew");
153 if (!hasSource(mediaElement
))
154 return paintMediaButton(paintInfo
.context
, rect
, mediaPlayDisabled
, false);
156 Image
* image
= !object
->node()->isMediaControlElement() || mediaControlElementType(object
->node()) == MediaPlayButton
? mediaPlay
: mediaPause
;
157 return paintMediaButton(paintInfo
.context
, rect
, image
);
160 bool MediaControlsPainter::paintMediaOverlayPlayButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
162 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
166 if (!hasSource(mediaElement
) || !mediaElement
->togglePlayStateWillPlay())
169 static Image
* mediaOverlayPlay
= platformResource("mediaplayerOverlayPlay",
170 "mediaplayerOverlayPlayNew");
172 IntRect
buttonRect(rect
);
173 if (RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
174 // Overlay play button covers the entire player, so center and draw a
175 // smaller button. Center in the entire element.
176 const LayoutBox
* box
= mediaElement
->layoutObject()->enclosingBox();
179 int mediaHeight
= box
->pixelSnappedHeight();
180 buttonRect
.setX(rect
.center().x() - mediaOverlayPlayButtonWidthNew
/ 2);
181 buttonRect
.setY(rect
.center().y() - mediaOverlayPlayButtonHeightNew
/ 2
182 + (mediaHeight
- rect
.height()) / 2);
183 buttonRect
.setWidth(mediaOverlayPlayButtonWidthNew
);
184 buttonRect
.setHeight(mediaOverlayPlayButtonHeightNew
);
187 return paintMediaButton(paintInfo
.context
, buttonRect
, mediaOverlayPlay
);
190 static Image
* getMediaSliderThumb()
192 static Image
* mediaSliderThumb
= platformResource("mediaplayerSliderThumb",
193 "mediaplayerSliderThumbNew");
194 return mediaSliderThumb
;
197 static void paintRoundedSliderBackground(const IntRect
& rect
, const ComputedStyle
& style
, GraphicsContext
* context
, Color sliderBackgroundColor
)
199 float borderRadius
= rect
.height() / 2;
200 FloatSize
radii(borderRadius
, borderRadius
);
202 context
->fillRoundedRect(FloatRoundedRect(rect
, radii
, radii
, radii
, radii
), sliderBackgroundColor
);
205 static void paintSliderRangeHighlight(const IntRect
& rect
, const ComputedStyle
& style
, GraphicsContext
* context
, int startPosition
, int endPosition
, Color startColor
, Color endColor
)
207 // Calculate border radius; need to avoid being smaller than half the slider height
208 // because of https://bugs.webkit.org/show_bug.cgi?id=30143.
209 float borderRadius
= rect
.height() / 2.0f
;
210 FloatSize
radii(borderRadius
, borderRadius
);
212 // Calculate highlight rectangle and edge dimensions.
213 int startOffset
= startPosition
;
214 int endOffset
= rect
.width() - endPosition
;
215 int rangeWidth
= endPosition
- startPosition
;
220 // Make sure the range width is bigger than border radius at the edges to retain rounded corners.
221 if (startOffset
< borderRadius
&& rangeWidth
< borderRadius
)
222 rangeWidth
= borderRadius
;
223 if (endOffset
< borderRadius
&& rangeWidth
< borderRadius
)
224 rangeWidth
= borderRadius
;
226 // Set rectangle to highlight range.
227 IntRect highlightRect
= rect
;
228 highlightRect
.move(startOffset
, 0);
229 highlightRect
.setWidth(rangeWidth
);
231 // Don't bother drawing an empty area.
232 if (highlightRect
.isEmpty())
235 // Calculate white-grey gradient.
236 FloatPoint sliderTopLeft
= highlightRect
.location();
237 FloatPoint sliderBottomLeft
= sliderTopLeft
;
238 sliderBottomLeft
.move(0, highlightRect
.height());
239 RefPtr
<Gradient
> gradient
= Gradient::create(sliderTopLeft
, sliderBottomLeft
);
240 gradient
->addColorStop(0.0, startColor
);
241 gradient
->addColorStop(1.0, endColor
);
243 // Fill highlight rectangle with gradient, potentially rounded if on left or right edge.
245 context
->setFillGradient(gradient
);
247 if (startOffset
< borderRadius
&& endOffset
< borderRadius
)
248 context
->fillRoundedRect(FloatRoundedRect(highlightRect
, radii
, radii
, radii
, radii
), startColor
);
249 else if (startOffset
< borderRadius
)
250 context
->fillRoundedRect(FloatRoundedRect(highlightRect
, radii
, IntSize(0, 0), radii
, IntSize(0, 0)), startColor
);
251 else if (endOffset
< borderRadius
)
252 context
->fillRoundedRect(FloatRoundedRect(highlightRect
, IntSize(0, 0), radii
, IntSize(0, 0), radii
), startColor
);
254 context
->fillRect(highlightRect
);
259 bool MediaControlsPainter::paintMediaSlider(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
261 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
265 GraphicsContext
* context
= paintInfo
.context
;
267 // Should we paint the slider partially transparent?
268 bool drawUiGrayed
= !hasSource(mediaElement
) && RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
270 context
->beginLayer(kDisabledAlpha
);
272 paintMediaSliderInternal(object
, paintInfo
, rect
);
280 void MediaControlsPainter::paintMediaSliderInternal(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
282 const bool useNewUi
= RuntimeEnabledFeatures::newMediaPlaybackUiEnabled();
283 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
287 const ComputedStyle
& style
= object
->styleRef();
288 GraphicsContext
* context
= paintInfo
.context
;
290 // Paint the slider bar in the "no data buffered" state.
291 Color sliderBackgroundColor
;
293 sliderBackgroundColor
= Color(11, 11, 11);
295 sliderBackgroundColor
= Color(0xda, 0xda, 0xda);
297 paintRoundedSliderBackground(rect
, style
, context
, sliderBackgroundColor
);
299 // Draw the buffered range. Since the element may have multiple buffered ranges and it'd be
300 // distracting/'busy' to show all of them, show only the buffered range containing the current play head.
301 TimeRanges
* bufferedTimeRanges
= mediaElement
->buffered();
302 float duration
= mediaElement
->duration();
303 float currentTime
= mediaElement
->currentTime();
304 if (std::isnan(duration
) || std::isinf(duration
) || !duration
|| std::isnan(currentTime
))
307 for (unsigned i
= 0; i
< bufferedTimeRanges
->length(); ++i
) {
308 float start
= bufferedTimeRanges
->start(i
, ASSERT_NO_EXCEPTION
);
309 float end
= bufferedTimeRanges
->end(i
, ASSERT_NO_EXCEPTION
);
310 // The delta is there to avoid corner cases when buffered
311 // ranges is out of sync with current time because of
312 // asynchronous media pipeline and current time caching in
314 // This is related to https://www.w3.org/Bugs/Public/show_bug.cgi?id=28125
315 // FIXME: Remove this workaround when WebMediaPlayer
316 // has an asynchronous pause interface.
317 if (std::isnan(start
) || std::isnan(end
)
318 || start
> currentTime
+ kCurrentTimeBufferedDelta
|| end
< currentTime
)
320 int startPosition
= int(start
* rect
.width() / duration
);
321 int currentPosition
= int(currentTime
* rect
.width() / duration
);
322 int endPosition
= int(end
* rect
.width() / duration
);
325 // Add half the thumb width proportionally adjusted to the current painting position.
326 int thumbCenter
= mediaSliderThumbWidth
/ 2;
327 int addWidth
= thumbCenter
* (1.0 - 2.0 * currentPosition
/ rect
.width());
328 currentPosition
+= addWidth
;
331 // Draw highlight before current time.
335 startColor
= Color(195, 195, 195); // white-ish.
336 endColor
= Color(217, 217, 217);
338 startColor
= endColor
= Color(0x42, 0x85, 0xf4); // blue.
341 if (currentPosition
> startPosition
)
342 paintSliderRangeHighlight(rect
, style
, context
, startPosition
, currentPosition
, startColor
, endColor
);
344 // Draw grey-ish highlight after current time.
346 startColor
= Color(60, 60, 60);
347 endColor
= Color(76, 76, 76);
349 startColor
= endColor
= Color(0x9f, 0x9f, 0x9f); // light grey.
352 if (endPosition
> currentPosition
)
353 paintSliderRangeHighlight(rect
, style
, context
, currentPosition
, endPosition
, startColor
, endColor
);
359 void MediaControlsPainter::adjustMediaSliderThumbPaintSize(const IntRect
& rect
, const ComputedStyle
& style
, IntRect
& rectOut
)
361 // Adjust the rectangle to be centered, the right size for the image.
362 // We do this because it's quite hard to get the thumb touch target
363 // to match. So, we provide the touch target size with
364 // adjustMediaSliderThumbSize(), and scale it back when we paint.
367 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
368 // ...except for the old UI.
372 float zoomLevel
= style
.effectiveZoom();
373 float zoomedPaintWidth
= mediaSliderThumbPaintWidthNew
* zoomLevel
;
374 float zoomedPaintHeight
= mediaSliderThumbPaintHeightNew
* zoomLevel
;
376 rectOut
.setX(rect
.center().x() - zoomedPaintWidth
/ 2);
377 rectOut
.setY(rect
.center().y() - zoomedPaintHeight
/ 2);
378 rectOut
.setWidth(zoomedPaintWidth
);
379 rectOut
.setHeight(zoomedPaintHeight
);
382 bool MediaControlsPainter::paintMediaSliderThumb(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
387 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
->node()->shadowHost());
391 if (!hasSource(mediaElement
))
394 Image
* mediaSliderThumb
= getMediaSliderThumb();
396 const ComputedStyle
& style
= object
->styleRef();
397 adjustMediaSliderThumbPaintSize(rect
, style
, paintRect
);
398 return paintMediaButton(paintInfo
.context
, paintRect
, mediaSliderThumb
);
401 bool MediaControlsPainter::paintMediaVolumeSlider(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
403 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
407 GraphicsContext
* context
= paintInfo
.context
;
408 const ComputedStyle
& style
= object
->styleRef();
410 // Paint the slider bar.
411 Color sliderBackgroundColor
;
412 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
413 sliderBackgroundColor
= Color(11, 11, 11);
415 sliderBackgroundColor
= Color(0x9f, 0x9f, 0x9f);
416 paintRoundedSliderBackground(rect
, style
, context
, sliderBackgroundColor
);
418 // Calculate volume position for white background rectangle.
419 float volume
= mediaElement
->volume();
420 if (std::isnan(volume
) || volume
< 0)
424 if (!hasSource(mediaElement
) || !mediaElement
->hasAudio() || mediaElement
->muted())
427 // Calculate the position relative to the center of the thumb.
429 if (!RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
431 float thumbCenter
= mediaVolumeSliderThumbWidth
/ 2;
432 float zoomLevel
= style
.effectiveZoom();
433 float positionWidth
= volume
* (rect
.width() - (zoomLevel
* thumbCenter
));
434 fillWidth
= positionWidth
+ (zoomLevel
* thumbCenter
/ 2);
437 fillWidth
= volume
* rect
.width();
440 Color startColor
= Color(195, 195, 195);
441 Color endColor
= Color(217, 217, 217);
442 if (RuntimeEnabledFeatures::newMediaPlaybackUiEnabled())
443 startColor
= endColor
= Color(0x42, 0x85, 0xf4); // blue.
445 paintSliderRangeHighlight(rect
, style
, context
, 0.0, fillWidth
, startColor
, endColor
);
450 bool MediaControlsPainter::paintMediaVolumeSliderThumb(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
455 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
->node()->shadowHost());
459 if (!hasSource(mediaElement
) || !mediaElement
->hasAudio())
462 static Image
* mediaVolumeSliderThumb
= platformResource(
463 "mediaplayerVolumeSliderThumb",
464 "mediaplayerVolumeSliderThumbNew");
467 const ComputedStyle
& style
= object
->styleRef();
468 adjustMediaSliderThumbPaintSize(rect
, style
, paintRect
);
469 return paintMediaButton(paintInfo
.context
, paintRect
, mediaVolumeSliderThumb
);
472 bool MediaControlsPainter::paintMediaFullscreenButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
474 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
478 // With the new player UI, we have separate assets for enter / exit
480 static Image
* mediaEnterFullscreenButton
= platformResource(
481 "mediaplayerFullscreen",
482 "mediaplayerEnterFullscreen");
483 static Image
* mediaExitFullscreenButton
= platformResource(
484 "mediaplayerFullscreen",
485 "mediaplayerExitFullscreen");
487 bool isEnabled
= hasSource(mediaElement
);
489 if (mediaControlElementType(object
->node()) == MediaExitFullscreenButton
)
490 return paintMediaButton(paintInfo
.context
, rect
, mediaExitFullscreenButton
, isEnabled
);
491 return paintMediaButton(paintInfo
.context
, rect
, mediaEnterFullscreenButton
, isEnabled
);
494 bool MediaControlsPainter::paintMediaToggleClosedCaptionsButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
496 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
500 static Image
* mediaClosedCaptionButton
= platformResource(
501 "mediaplayerClosedCaption", "mediaplayerClosedCaptionNew");
502 static Image
* mediaClosedCaptionButtonDisabled
= platformResource(
503 "mediaplayerClosedCaptionDisabled",
504 "mediaplayerClosedCaptionDisabledNew");
506 bool isEnabled
= mediaElement
->hasClosedCaptions();
508 if (mediaElement
->closedCaptionsVisible())
509 return paintMediaButton(paintInfo
.context
, rect
, mediaClosedCaptionButton
, isEnabled
);
511 return paintMediaButton(paintInfo
.context
, rect
, mediaClosedCaptionButtonDisabled
, isEnabled
);
514 bool MediaControlsPainter::paintMediaCastButton(LayoutObject
* object
, const PaintInfo
& paintInfo
, const IntRect
& rect
)
516 HTMLMediaElement
* mediaElement
= toParentMediaElement(object
);
520 static Image
* mediaCastOn
= platformResource("mediaplayerCastOn", "mediaplayerCastOnNew");
521 static Image
* mediaCastOff
= platformResource("mediaplayerCastOff", "mediaplayerCastOffNew");
522 // To ensure that the overlaid cast button is visible when overlaid on pale videos we use a
523 // different version of it for the overlaid case with a semi-opaque background.
524 static Image
* mediaOverlayCastOff
= platformResource(
525 "mediaplayerOverlayCastOff",
526 "mediaplayerOverlayCastOffNew");
528 bool isEnabled
= mediaElement
->hasRemoteRoutes();
530 switch (mediaControlElementType(object
->node())) {
531 case MediaCastOnButton
:
532 return paintMediaButton(paintInfo
.context
, rect
, mediaCastOn
, isEnabled
);
533 case MediaOverlayCastOnButton
:
534 return paintMediaButton(paintInfo
.context
, rect
, mediaCastOn
);
535 case MediaCastOffButton
:
536 return paintMediaButton(paintInfo
.context
, rect
, mediaCastOff
, isEnabled
);
537 case MediaOverlayCastOffButton
:
538 return paintMediaButton(paintInfo
.context
, rect
, mediaOverlayCastOff
);
540 ASSERT_NOT_REACHED();
545 void MediaControlsPainter::adjustMediaSliderThumbSize(ComputedStyle
& style
)
547 static Image
* mediaSliderThumb
= platformResource("mediaplayerSliderThumb",
548 "mediaplayerSliderThumbNew");
549 static Image
* mediaVolumeSliderThumb
= platformResource(
550 "mediaplayerVolumeSliderThumb",
551 "mediaplayerVolumeSliderThumbNew");
555 Image
* thumbImage
= 0;
557 if (RuntimeEnabledFeatures::newMediaPlaybackUiEnabled()) {
558 // Volume and time sliders are the same.
559 thumbImage
= mediaSliderThumb
;
560 width
= mediaSliderThumbTouchWidthNew
;
561 height
= mediaSliderThumbTouchHeightNew
;
562 } else if (style
.appearance() == MediaSliderThumbPart
) {
563 thumbImage
= mediaSliderThumb
;
564 width
= mediaSliderThumbWidth
;
565 height
= mediaSliderThumbHeight
;
566 } else if (style
.appearance() == MediaVolumeSliderThumbPart
) {
567 thumbImage
= mediaVolumeSliderThumb
;
568 width
= mediaVolumeSliderThumbWidth
;
569 height
= mediaVolumeSliderThumbHeight
;
572 float zoomLevel
= style
.effectiveZoom();
574 style
.setWidth(Length(static_cast<int>(width
* zoomLevel
), Fixed
));
575 style
.setHeight(Length(static_cast<int>(height
* zoomLevel
), Fixed
));