2 * Copyright (C) 2005-2017 Apple Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23 * THE POSSIBILITY OF SUCH DAMAGE.
27 #import "RenderThemeIOS.h"
31 #import "BitmapImage.h"
32 #import "CSSPrimitiveValue.h"
33 #import "CSSToLengthConversionData.h"
34 #import "CSSValueKeywords.h"
35 #import "DateComponents.h"
38 #import "FloatRoundedRect.h"
40 #import "FontCascade.h"
42 #import "FrameSelection.h"
44 #import "GeometryUtilities.h"
46 #import "GraphicsContext.h"
47 #import "GraphicsContextCG.h"
48 #import "HTMLAttachmentElement.h"
49 #import "HTMLInputElement.h"
51 #import "HTMLSelectElement.h"
54 #import "LocalizedDateCache.h"
55 #import "NodeRenderStyle.h"
58 #import "PathUtilities.h"
59 #import "PlatformLocale.h"
60 #import "RenderAttachment.h"
61 #import "RenderObject.h"
62 #import "RenderProgress.h"
63 #import "RenderStyle.h"
64 #import "RenderView.h"
65 #import "RuntimeEnabledFeatures.h"
66 #import "UTIUtilities.h"
67 #import "UserAgentScripts.h"
68 #import "UserAgentStyleSheets.h"
69 #import "WebCoreThreadRun.h"
70 #import <CoreGraphics/CoreGraphics.h>
71 #import <CoreImage/CoreImage.h>
72 #import <objc/runtime.h>
73 #import <pal/spi/cocoa/CoreTextSPI.h>
74 #import <pal/spi/ios/UIKitSPI.h>
75 #import <wtf/NeverDestroyed.h>
76 #import <wtf/RefPtr.h>
77 #import <wtf/SoftLinking.h>
78 #import <wtf/StdLibExtras.h>
80 #if USE(SYSTEM_PREVIEW) && USE(APPLE_INTERNAL_SDK)
81 #import <WebKitAdditions/SystemPreviewArtwork.cpp>
84 SOFT_LINK_FRAMEWORK(UIKit)
85 SOFT_LINK_CLASS(UIKit, UIApplication)
86 SOFT_LINK_CLASS(UIKit, UIColor)
87 SOFT_LINK_CLASS(UIKit, UIDocumentInteractionController)
88 SOFT_LINK_CLASS(UIKit, UIImage)
89 SOFT_LINK_CONSTANT(UIKit, UIContentSizeCategoryDidChangeNotification, CFStringRef)
90 #define UIContentSizeCategoryDidChangeNotification getUIContentSizeCategoryDidChangeNotification()
92 @interface WebCoreRenderThemeBundle : NSObject
95 @implementation WebCoreRenderThemeBundle
100 using namespace HTMLNames;
102 const float ControlBaseHeight = 20;
103 const float ControlBaseFontSize = 11;
106 float* start; // points to static float[4]
107 float* end; // points to static float[4]
108 IOSGradient(float start[4], float end[4])
115 typedef IOSGradient* IOSGradientRef;
120 ExponentialInterpolation
123 static void interpolateLinearGradient(void *info, const CGFloat *inData, CGFloat *outData)
125 IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
126 float alpha = inData[0];
127 float inverse = 1.0f - alpha;
129 outData[0] = inverse * gradient->start[0] + alpha * gradient->end[0];
130 outData[1] = inverse * gradient->start[1] + alpha * gradient->end[1];
131 outData[2] = inverse * gradient->start[2] + alpha * gradient->end[2];
132 outData[3] = inverse * gradient->start[3] + alpha * gradient->end[3];
135 static void interpolateExponentialGradient(void *info, const CGFloat *inData, CGFloat *outData)
137 IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
139 for (int paintInfo = 0; paintInfo < 4; ++paintInfo) {
140 float end = logf(std::max(gradient->end[paintInfo], 0.01f));
141 float start = logf(std::max(gradient->start[paintInfo], 0.01f));
142 outData[paintInfo] = expf(start - (end + start) * a);
146 static CGFunctionRef getSharedFunctionRef(IOSGradientRef gradient, Interpolation interpolation)
148 CGFunctionRef function = nullptr;
150 static HashMap<IOSGradientRef, CGFunctionRef>* linearFunctionRefs;
151 static HashMap<IOSGradientRef, CGFunctionRef>* exponentialFunctionRefs;
153 if (interpolation == LinearInterpolation) {
154 if (!linearFunctionRefs)
155 linearFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
157 function = linearFunctionRefs->get(gradient);
160 static struct CGFunctionCallbacks linearFunctionCallbacks = { 0, interpolateLinearGradient, 0 };
161 linearFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, nullptr, 4, nullptr, &linearFunctionCallbacks));
167 if (!exponentialFunctionRefs)
168 exponentialFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
170 function = exponentialFunctionRefs->get(gradient);
173 static struct CGFunctionCallbacks exponentialFunctionCallbacks = { 0, interpolateExponentialGradient, 0 };
174 exponentialFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, 0, 4, 0, &exponentialFunctionCallbacks));
180 static void drawAxialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, const FloatPoint& stopPoint, Interpolation interpolation)
182 RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateAxial(sRGBColorSpaceRef(), startPoint, stopPoint, getSharedFunctionRef(gradient, interpolation), false, false));
183 CGContextDrawShading(context, shading.get());
186 static void drawRadialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, float startRadius, const FloatPoint& stopPoint, float stopRadius, Interpolation interpolation)
188 RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateRadial(sRGBColorSpaceRef(), startPoint, startRadius, stopPoint, stopRadius, getSharedFunctionRef(gradient, interpolation), false, false));
189 CGContextDrawShading(context, shading.get());
192 enum IOSGradientType {
199 ReadonlySliderTrackGradient,
200 SliderThumbOpaquePressedGradient,
203 static IOSGradientRef getInsetGradient()
205 static float end[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0 };
206 static float start[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0.2 };
207 static NeverDestroyed<IOSGradient> gradient(start, end);
208 return &gradient.get();
211 static IOSGradientRef getShineGradient()
213 static float end[4] = { 1, 1, 1, 0.8 };
214 static float start[4] = { 1, 1, 1, 0 };
215 static NeverDestroyed<IOSGradient> gradient(start, end);
216 return &gradient.get();
219 static IOSGradientRef getShadeGradient()
221 static float end[4] = { 178 / 255.0, 178 / 255.0, 178 / 255.0, 0.65 };
222 static float start[4] = { 252 / 255.0, 252 / 255.0, 252 / 255.0, 0.65 };
223 static NeverDestroyed<IOSGradient> gradient(start, end);
224 return &gradient.get();
227 static IOSGradientRef getConvexGradient()
229 static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.05 };
230 static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.43 };
231 static NeverDestroyed<IOSGradient> gradient(start, end);
232 return &gradient.get();
235 static IOSGradientRef getConcaveGradient()
237 static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.46 };
238 static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0 };
239 static NeverDestroyed<IOSGradient> gradient(start, end);
240 return &gradient.get();
243 static IOSGradientRef getSliderTrackGradient()
245 static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 1 };
246 static float start[4] = { 74 / 255.0, 77 / 255.0, 80 / 255.0, 1 };
247 static NeverDestroyed<IOSGradient> gradient(start, end);
248 return &gradient.get();
251 static IOSGradientRef getReadonlySliderTrackGradient()
253 static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 0.4 };
254 static float start[4] = { 74 / 255.0, 77 / 255.0, 80 /255.0, 0.4 };
255 static NeverDestroyed<IOSGradient> gradient(start, end);
256 return &gradient.get();
259 static IOSGradientRef getSliderThumbOpaquePressedGradient()
261 static float end[4] = { 144 / 255.0, 144 / 255.0, 144 / 255.0, 1};
262 static float start[4] = { 55 / 255.0, 55 / 255.0, 55 / 255.0, 1 };
263 static NeverDestroyed<IOSGradient> gradient(start, end);
264 return &gradient.get();
267 static IOSGradientRef gradientWithName(IOSGradientType gradientType)
269 switch (gradientType) {
271 return getInsetGradient();
273 return getShineGradient();
275 return getShadeGradient();
277 return getConvexGradient();
278 case ConcaveGradient:
279 return getConcaveGradient();
280 case SliderTrackGradient:
281 return getSliderTrackGradient();
282 case ReadonlySliderTrackGradient:
283 return getReadonlySliderTrackGradient();
284 case SliderThumbOpaquePressedGradient:
285 return getSliderThumbOpaquePressedGradient();
287 ASSERT_NOT_REACHED();
291 static void contentSizeCategoryDidChange(CFNotificationCenterRef, void*, CFStringRef name, const void*, CFDictionaryRef)
293 ASSERT_UNUSED(name, CFEqual(name, UIContentSizeCategoryDidChangeNotification));
295 Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
299 RenderThemeIOS::RenderThemeIOS()
301 CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, contentSizeCategoryDidChange, UIContentSizeCategoryDidChangeNotification, 0, CFNotificationSuspensionBehaviorDeliverImmediately);
304 RenderTheme& RenderTheme::singleton()
306 static NeverDestroyed<RenderThemeIOS> theme;
310 static String& _contentSizeCategory()
312 static NeverDestroyed<String> _contentSizeCategory;
313 return _contentSizeCategory.get();
316 CFStringRef RenderThemeIOS::contentSizeCategory()
318 if (!_contentSizeCategory().isNull())
319 return (__bridge CFStringRef)static_cast<NSString*>(_contentSizeCategory());
320 return (CFStringRef)[[getUIApplicationClass() sharedApplication] preferredContentSizeCategory];
323 void RenderThemeIOS::setContentSizeCategory(const String& contentSizeCategory)
325 _contentSizeCategory() = contentSizeCategory;
328 const Color& RenderThemeIOS::shadowColor() const
330 static NeverDestroyed<Color> color(0.0f, 0.0f, 0.0f, 0.7f);
334 FloatRect RenderThemeIOS::addRoundedBorderClip(const RenderObject& box, GraphicsContext& context, const IntRect& rect)
336 // To fix inner border bleeding issues <rdar://problem/9812507>, we clip to the outer border and assert that
337 // the border is opaque or transparent, unless we're checked because checked radio/checkboxes show no bleeding.
338 auto& style = box.style();
339 RoundedRect border = isChecked(box) ? style.getRoundedInnerBorderFor(rect) : style.getRoundedBorderFor(rect);
341 if (border.isRounded())
342 context.clipRoundedRect(FloatRoundedRect(border));
344 context.clip(border.rect());
346 if (isChecked(box)) {
347 ASSERT(style.visitedDependentColor(CSSPropertyBorderTopColor).alpha() % 255 == 0);
348 ASSERT(style.visitedDependentColor(CSSPropertyBorderRightColor).alpha() % 255 == 0);
349 ASSERT(style.visitedDependentColor(CSSPropertyBorderBottomColor).alpha() % 255 == 0);
350 ASSERT(style.visitedDependentColor(CSSPropertyBorderLeftColor).alpha() % 255 == 0);
353 return border.rect();
356 void RenderThemeIOS::adjustCheckboxStyle(StyleResolver&, RenderStyle& style, const Element*) const
358 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
361 int size = std::max(style.computedFontPixelSize(), 10U);
362 style.setWidth({ size, Fixed });
363 style.setHeight({ size, Fixed });
366 static CGPoint shortened(CGPoint start, CGPoint end, float width)
368 float x = end.x - start.x;
369 float y = end.y - start.y;
370 float ratio = (!x && !y) ? 0 : width / sqrtf(x * x + y * y);
371 return CGPointMake(start.x + x * ratio, start.y + y * ratio);
374 static void drawJoinedLines(CGContextRef context, CGPoint points[], unsigned count, bool antialias, CGLineCap lineCap)
376 CGContextSetShouldAntialias(context, antialias);
377 CGContextBeginPath(context);
378 CGContextSetLineCap(context, lineCap);
379 CGContextMoveToPoint(context, points[0].x, points[0].y);
381 for (unsigned i = 1; i < count; ++i)
382 CGContextAddLineToPoint(context, points[i].x, points[i].y);
384 CGContextStrokePath(context);
387 bool RenderThemeIOS::paintCheckboxDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
389 GraphicsContextStateSaver stateSaver(paintInfo.context());
390 FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
392 float width = clip.width();
393 float height = clip.height();
395 CGContextRef cgContext = paintInfo.context().platformContext();
396 if (isChecked(box)) {
397 drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
399 static const float thicknessRatio = 2 / 14.0;
400 static const CGSize size = { 14.0f, 14.0f };
401 static const CGPoint pathRatios[3] = {
402 { 2.5f / size.width, 7.5f / size.height },
403 { 5.5f / size.width, 10.5f / size.height },
404 { 11.5f / size.width, 2.5f / size.height }
407 float lineWidth = std::min(width, height) * 2.0f * thicknessRatio;
410 CGPointMake(clip.x() + width * pathRatios[0].x, clip.y() + height * pathRatios[0].y),
411 CGPointMake(clip.x() + width * pathRatios[1].x, clip.y() + height * pathRatios[1].y),
412 CGPointMake(clip.x() + width * pathRatios[2].x, clip.y() + height * pathRatios[2].y)
414 CGPoint shadow[3] = {
415 shortened(line[0], line[1], lineWidth / 4.0f),
417 shortened(line[2], line[1], lineWidth / 4.0f)
420 lineWidth = std::max<float>(lineWidth, 1);
421 CGContextSetLineWidth(cgContext, lineWidth);
422 CGContextSetStrokeColorWithColor(cgContext, cachedCGColor(Color(0.0f, 0.0f, 0.0f, 0.7f)));
423 drawJoinedLines(cgContext, shadow, 3, true, kCGLineCapSquare);
425 lineWidth = std::max<float>(std::min(clip.width(), clip.height()) * thicknessRatio, 1);
426 CGContextSetLineWidth(cgContext, lineWidth);
427 CGContextSetStrokeColorWithColor(cgContext, cachedCGColor(Color(1.0f, 1.0f, 1.0f, 240 / 255.0f)));
428 drawJoinedLines(cgContext, line, 3, true, kCGLineCapButt);
430 FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
431 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
432 drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, sqrtf((width * width) / 4.0f + height * height), ExponentialInterpolation);
438 int RenderThemeIOS::baselinePosition(const RenderBox& box) const
440 if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
441 return box.marginTop() + box.height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
442 if (box.style().appearance() == MenulistPart)
443 return box.marginTop() + box.height() - 5; // This is to match AppKit. There might be a better way to calculate this though.
444 return RenderTheme::baselinePosition(box);
447 bool RenderThemeIOS::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
449 // Buttons and MenulistButtons are styled if they contain a background image.
450 if (style.appearance() == PushButtonPart || style.appearance() == MenulistButtonPart)
451 return !style.visitedDependentColor(CSSPropertyBackgroundColor).isVisible() || style.backgroundLayers().hasImage();
453 if (style.appearance() == TextFieldPart || style.appearance() == TextAreaPart)
454 return style.backgroundLayers() != background;
456 return RenderTheme::isControlStyled(style, border, background, backgroundColor);
459 void RenderThemeIOS::adjustRadioStyle(StyleResolver&, RenderStyle& style, const Element*) const
461 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
464 int size = std::max(style.computedFontPixelSize(), 10U);
465 style.setWidth({ size, Fixed });
466 style.setHeight({ size, Fixed });
467 style.setBorderRadius({ size / 2, size / 2 });
470 bool RenderThemeIOS::paintRadioDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
472 GraphicsContextStateSaver stateSaver(paintInfo.context());
473 FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
475 CGContextRef cgContext = paintInfo.context().platformContext();
476 if (isChecked(box)) {
477 drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
479 // The inner circle is 6 / 14 the size of the surrounding circle,
480 // leaving 8 / 14 around it. (8 / 14) / 2 = 2 / 7.
482 static const float InnerInverseRatio = 2 / 7.0;
484 clip.inflateX(-clip.width() * InnerInverseRatio);
485 clip.inflateY(-clip.height() * InnerInverseRatio);
487 paintInfo.context().drawRaisedEllipse(clip, Color::white, shadowColor());
489 FloatSize radius(clip.width() / 2.0f, clip.height() / 2.0f);
490 paintInfo.context().clipRoundedRect(FloatRoundedRect(clip, radius, radius, radius, radius));
492 FloatPoint bottomCenter(clip.x() + clip.width() / 2.0, clip.maxY());
493 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
494 drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
498 bool RenderThemeIOS::paintTextFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
500 auto& style = box.style();
501 FloatPoint point(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth());
503 GraphicsContextStateSaver stateSaver(paintInfo.context());
505 paintInfo.context().clipRoundedRect(style.getRoundedBorderFor(LayoutRect(rect)).pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()));
507 // This gradient gets drawn black when printing.
508 // Do not draw the gradient if there is no visible top border.
509 bool topBorderIsInvisible = !style.hasBorder() || !style.borderTopWidth() || style.borderTopIsTransparent();
510 if (!box.view().printing() && !topBorderIsInvisible)
511 drawAxialGradient(paintInfo.context().platformContext(), gradientWithName(InsetGradient), point, FloatPoint(CGPointMake(point.x(), point.y() + 3.0f)), LinearInterpolation);
515 bool RenderThemeIOS::paintTextAreaDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
517 return paintTextFieldDecorations(box, paintInfo, rect);
520 const int MenuListMinHeight = 15;
522 const float MenuListBaseHeight = 20;
523 const float MenuListBaseFontSize = 11;
525 const float MenuListArrowWidth = 7;
526 const float MenuListArrowHeight = 6;
527 const float MenuListButtonPaddingAfter = 19;
529 LengthBox RenderThemeIOS::popupInternalPaddingBox(const RenderStyle& style) const
531 if (style.appearance() == MenulistButtonPart) {
532 if (style.direction() == RTL)
533 return { 0, 0, 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()) };
534 return { 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()), 0, 0 };
536 return { 0, 0, 0, 0 };
539 void RenderThemeIOS::adjustRoundBorderRadius(RenderStyle& style, RenderBox& box)
541 if (style.appearance() == NoControlPart || style.backgroundLayers().hasImage())
544 // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>.
545 style.setBorderRadius({ { std::min(box.width(), box.height()) / 2, Fixed }, { box.height() / 2, Fixed } });
548 static void applyCommonButtonPaddingToStyle(RenderStyle& style, const Element& element)
550 Document& document = element.document();
551 RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(0.5, CSSPrimitiveValue::CSS_EMS);
552 int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, document.renderStyle(), document.renderView(), document.frame()->pageZoomFactor()));
553 style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
556 static void adjustSelectListButtonStyle(RenderStyle& style, const Element& element)
558 // Enforce "padding: 0 0.5em".
559 applyCommonButtonPaddingToStyle(style, element);
561 // Enforce "line-height: normal".
562 style.setLineHeight(Length(-100.0, Percent));
565 class RenderThemeMeasureTextClient : public MeasureTextClient {
567 RenderThemeMeasureTextClient(const FontCascade& font, const RenderStyle& style)
572 float measureText(const String& string) const override
574 TextRun run = RenderBlock::constructTextRun(string, m_style);
575 return m_font.width(run);
578 const FontCascade& m_font;
579 const RenderStyle& m_style;
582 static void adjustInputElementButtonStyle(RenderStyle& style, const HTMLInputElement& inputElement)
584 // Always Enforce "padding: 0 0.5em".
585 applyCommonButtonPaddingToStyle(style, inputElement);
587 // Don't adjust the style if the width is specified.
588 if (style.width().isFixed() && style.width().value() > 0)
591 // Don't adjust for unsupported date input types.
592 DateComponents::Type dateType = inputElement.dateType();
593 if (dateType == DateComponents::Invalid || dateType == DateComponents::Week)
596 // Enforce the width and set the box-sizing to content-box to not conflict with the padding.
597 FontCascade font = style.fontCascade();
599 float maximumWidth = localizedDateCache().maximumWidthForDateType(dateType, font, RenderThemeMeasureTextClient(font, style));
601 ASSERT(maximumWidth >= 0);
603 if (maximumWidth > 0) {
604 int width = static_cast<int>(maximumWidth + MenuListButtonPaddingAfter);
605 style.setWidth(Length(width, Fixed));
606 style.setBoxSizing(BoxSizing::ContentBox);
610 void RenderThemeIOS::adjustMenuListButtonStyle(StyleResolver&, RenderStyle& style, const Element* element) const
612 // Set the min-height to be at least MenuListMinHeight.
613 if (style.height().isAuto())
614 style.setMinHeight(Length(std::max(MenuListMinHeight, static_cast<int>(MenuListBaseHeight / MenuListBaseFontSize * style.fontDescription().computedSize())), Fixed));
616 style.setMinHeight(Length(MenuListMinHeight, Fixed));
621 // Enforce some default styles in the case that this is a non-multiple <select> element,
622 // or a date input. We don't force these if this is just an element with
623 // "-webkit-appearance: menulist-button".
624 if (is<HTMLSelectElement>(*element) && !element->hasAttributeWithoutSynchronization(HTMLNames::multipleAttr))
625 adjustSelectListButtonStyle(style, *element);
626 else if (is<HTMLInputElement>(*element))
627 adjustInputElementButtonStyle(style, downcast<HTMLInputElement>(*element));
630 bool RenderThemeIOS::paintMenuListButtonDecorations(const RenderBox& box, const PaintInfo& paintInfo, const FloatRect& rect)
632 auto& style = box.style();
633 bool isRTL = style.direction() == RTL;
634 float borderTopWidth = style.borderTopWidth();
635 FloatRect clip(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth(), rect.width() - style.borderLeftWidth() - style.borderRightWidth(), rect.height() - style.borderTopWidth() - style.borderBottomWidth());
636 CGContextRef cgContext = paintInfo.context().platformContext();
638 float adjustLeft = 0.5;
639 float adjustRight = 0.5;
640 float adjustTop = 0.5;
641 float adjustBottom = 0.5;
643 // Paint title portion.
645 float leftInset = isRTL ? MenuListButtonPaddingAfter : 0;
646 FloatRect titleClip(clip.x() + leftInset - adjustLeft, clip.y() - adjustTop, clip.width() - MenuListButtonPaddingAfter + adjustLeft, clip.height() + adjustTop + adjustBottom);
648 GraphicsContextStateSaver stateSaver(paintInfo.context());
650 FloatSize topLeftRadius;
651 FloatSize topRightRadius;
652 FloatSize bottomLeftRadius;
653 FloatSize bottomRightRadius;
656 topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height, rect.height()) - style.borderTopWidth());
657 bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height, rect.height()) - style.borderBottomWidth());
659 topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height, rect.height()) - style.borderTopWidth());
660 bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height, rect.height()) - style.borderBottomWidth());
663 paintInfo.context().clipRoundedRect(FloatRoundedRect(titleClip,
664 topLeftRadius, topRightRadius,
665 bottomLeftRadius, bottomRightRadius));
667 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), titleClip.location(), FloatPoint(titleClip.x(), titleClip.maxY()), LinearInterpolation);
668 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(titleClip.x(), titleClip.maxY()), titleClip.location(), ExponentialInterpolation);
671 // Draw the separator after the initial padding.
673 float separatorPosition = isRTL ? (clip.x() + MenuListButtonPaddingAfter) : (clip.maxX() - MenuListButtonPaddingAfter);
675 box.drawLineForBoxSide(paintInfo.context(), FloatRect(FloatPoint(separatorPosition - borderTopWidth, clip.y()), FloatPoint(separatorPosition, clip.maxY())), BSRight, style.visitedDependentColor(CSSPropertyBorderTopColor), style.borderTopStyle(), 0, 0);
677 FloatRect buttonClip;
679 buttonClip = FloatRect(clip.x() - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustLeft, clip.height() + adjustTop + adjustBottom);
681 buttonClip = FloatRect(separatorPosition - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustRight, clip.height() + adjustTop + adjustBottom);
683 // Now paint the button portion.
685 GraphicsContextStateSaver stateSaver(paintInfo.context());
687 FloatSize topLeftRadius;
688 FloatSize topRightRadius;
689 FloatSize bottomLeftRadius;
690 FloatSize bottomRightRadius;
693 topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height, rect.height()) - style.borderTopWidth());
694 bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width, rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height, rect.height()) - style.borderBottomWidth());
696 topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height, rect.height()) - style.borderTopWidth());
697 bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width, rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height, rect.height()) - style.borderBottomWidth());
700 paintInfo.context().clipRoundedRect(FloatRoundedRect(buttonClip,
701 topLeftRadius, topRightRadius,
702 bottomLeftRadius, bottomRightRadius));
704 paintInfo.context().fillRect(buttonClip, style.visitedDependentColor(CSSPropertyBorderTopColor));
706 drawAxialGradient(cgContext, gradientWithName(isFocused(box) && !isReadOnlyControl(box) ? ConcaveGradient : ConvexGradient), buttonClip.location(), FloatPoint(buttonClip.x(), buttonClip.maxY()), LinearInterpolation);
711 if (box.isMenuList() && downcast<HTMLSelectElement>(box.element())->multiple()) {
716 FloatRect ellipse(buttonClip.x() + (buttonClip.width() - count * (size + padding) + padding) / 2.0, buttonClip.maxY() - 10.0, size, size);
718 for (int i = 0; i < count; ++i) {
719 paintInfo.context().drawRaisedEllipse(ellipse, Color::white, Color(0.0f, 0.0f, 0.0f, 0.5f));
720 ellipse.move(size + padding, 0);
723 float centerX = floorf(buttonClip.x() + buttonClip.width() / 2.0) - 0.5;
724 float centerY = floorf(buttonClip.y() + buttonClip.height() * 3.0 / 8.0);
726 Vector<FloatPoint> arrow = {
727 { centerX - MenuListArrowWidth / 2, centerY },
728 { centerX + MenuListArrowWidth / 2, centerY },
729 { centerX, centerY + MenuListArrowHeight }
732 Vector<FloatPoint> shadow = {
733 { arrow[0].x(), arrow[0].y() + 1 },
734 { arrow[1].x(), arrow[1].y() + 1 },
735 { arrow[2].x(), arrow[2].y() + 1 }
738 float opacity = isReadOnlyControl(box) ? 0.2 : 0.5;
739 paintInfo.context().setStrokeColor(Color(0.0f, 0.0f, 0.0f, opacity));
740 paintInfo.context().setFillColor(Color(0.0f, 0.0f, 0.0f, opacity));
741 paintInfo.context().drawPath(Path::polygonPathFromPoints(shadow));
743 paintInfo.context().setStrokeColor(Color::white);
744 paintInfo.context().setFillColor(Color::white);
745 paintInfo.context().drawPath(Path::polygonPathFromPoints(arrow));
751 const CGFloat kTrackThickness = 4.0;
752 const CGFloat kTrackRadius = kTrackThickness / 2.0;
753 const int kDefaultSliderThumbSize = 16;
755 void RenderThemeIOS::adjustSliderTrackStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
757 RenderTheme::adjustSliderTrackStyle(selector, style, element);
759 // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>.
760 int radius = static_cast<int>(kTrackRadius);
761 style.setBorderRadius({ { radius, Fixed }, { radius, Fixed } });
764 bool RenderThemeIOS::paintSliderTrack(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
766 IntRect trackClip = rect;
767 auto& style = box.style();
769 bool isHorizontal = true;
770 switch (style.appearance()) {
771 case SliderHorizontalPart:
773 // Inset slightly so the thumb covers the edge.
774 if (trackClip.width() > 2) {
775 trackClip.setWidth(trackClip.width() - 2);
776 trackClip.setX(trackClip.x() + 1);
778 trackClip.setHeight(static_cast<int>(kTrackThickness));
779 trackClip.setY(rect.y() + rect.height() / 2 - kTrackThickness / 2);
781 case SliderVerticalPart:
782 isHorizontal = false;
783 // Inset slightly so the thumb covers the edge.
784 if (trackClip.height() > 2) {
785 trackClip.setHeight(trackClip.height() - 2);
786 trackClip.setY(trackClip.y() + 1);
788 trackClip.setWidth(kTrackThickness);
789 trackClip.setX(rect.x() + rect.width() / 2 - kTrackThickness / 2);
792 ASSERT_NOT_REACHED();
795 ASSERT(trackClip.width() >= 0);
796 ASSERT(trackClip.height() >= 0);
797 CGFloat cornerWidth = trackClip.width() < kTrackThickness ? trackClip.width() / 2.0f : kTrackRadius;
798 CGFloat cornerHeight = trackClip.height() < kTrackThickness ? trackClip.height() / 2.0f : kTrackRadius;
800 bool readonly = isReadOnlyControl(box);
802 #if ENABLE(DATALIST_ELEMENT)
803 paintSliderTicks(box, paintInfo, trackClip);
806 // Draw the track gradient.
808 GraphicsContextStateSaver stateSaver(paintInfo.context());
810 IntSize cornerSize(cornerWidth, cornerHeight);
811 FloatRoundedRect innerBorder(trackClip, cornerSize, cornerSize, cornerSize, cornerSize);
812 paintInfo.context().clipRoundedRect(innerBorder);
814 CGContextRef cgContext = paintInfo.context().platformContext();
815 IOSGradientRef gradient = readonly ? gradientWithName(ReadonlySliderTrackGradient) : gradientWithName(SliderTrackGradient);
817 drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.x(), trackClip.maxY()), LinearInterpolation);
819 drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.maxX(), trackClip.y()), LinearInterpolation);
822 // Draw the track border.
824 GraphicsContextStateSaver stateSaver(paintInfo.context());
826 CGContextRef cgContext = paintInfo.context().platformContext();
828 paintInfo.context().setStrokeColor(Color(178, 178, 178));
830 paintInfo.context().setStrokeColor(Color(76, 76, 76));
832 RetainPtr<CGMutablePathRef> roundedRectPath = adoptCF(CGPathCreateMutable());
833 CGPathAddRoundedRect(roundedRectPath.get(), 0, trackClip, cornerWidth, cornerHeight);
834 CGContextAddPath(cgContext, roundedRectPath.get());
835 CGContextSetLineWidth(cgContext, 1);
836 CGContextStrokePath(cgContext);
842 void RenderThemeIOS::adjustSliderThumbSize(RenderStyle& style, const Element*) const
844 if (style.appearance() != SliderThumbHorizontalPart && style.appearance() != SliderThumbVerticalPart)
847 // Enforce "border-radius: 50%".
848 style.setBorderRadius({ { 50, Percent }, { 50, Percent } });
850 // Enforce a 16x16 size if no size is provided.
851 if (style.width().isIntrinsicOrAuto() || style.height().isAuto()) {
852 style.setWidth({ kDefaultSliderThumbSize, Fixed });
853 style.setHeight({ kDefaultSliderThumbSize, Fixed });
857 bool RenderThemeIOS::paintSliderThumbDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
859 GraphicsContextStateSaver stateSaver(paintInfo.context());
860 FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
862 CGContextRef cgContext = paintInfo.context().platformContext();
863 FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
865 drawAxialGradient(cgContext, gradientWithName(SliderThumbOpaquePressedGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
867 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
868 drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0.0f, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
874 Seconds RenderThemeIOS::animationRepeatIntervalForProgressBar(RenderProgress&) const
879 Seconds RenderThemeIOS::animationDurationForProgressBar(RenderProgress&) const
884 bool RenderThemeIOS::paintProgressBar(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& rect)
886 if (!is<RenderProgress>(renderer))
889 const int progressBarHeight = 9;
890 const float verticalOffset = (rect.height() - progressBarHeight) / 2.0;
892 GraphicsContextStateSaver stateSaver(paintInfo.context());
893 if (rect.width() < 10 || rect.height() < 9) {
894 // The rect is smaller than the standard progress bar. We clip to the element's rect to avoid
895 // leaking pixels outside the repaint rect.
896 paintInfo.context().clip(rect);
899 // 1) Draw the progress bar track.
900 // 1.1) Draw the white background with grey gradient border.
901 GraphicsContext& context = paintInfo.context();
902 context.setStrokeThickness(0.68);
903 context.setStrokeStyle(SolidStroke);
905 const float verticalRenderingPosition = rect.y() + verticalOffset;
906 RefPtr<Gradient> strokeGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
907 strokeGradient->addColorStop(0.0, Color(0x8d, 0x8d, 0x8d));
908 strokeGradient->addColorStop(0.45, Color(0xee, 0xee, 0xee));
909 strokeGradient->addColorStop(0.55, Color(0xee, 0xee, 0xee));
910 strokeGradient->addColorStop(1.0, Color(0x8d, 0x8d, 0x8d));
911 context.setStrokeGradient(strokeGradient.releaseNonNull());
913 context.setFillColor(Color(255, 255, 255));
916 FloatRect trackRect(rect.x() + 0.25, verticalRenderingPosition + 0.25, rect.width() - 0.5, progressBarHeight - 0.5);
917 FloatSize roundedCornerRadius(5, 4);
918 trackPath.addRoundedRect(trackRect, roundedCornerRadius);
919 context.drawPath(trackPath);
921 // 1.2) Draw top gradient on the upper half. It is supposed to overlay the fill from the background and darker the stroked path.
922 FloatRect border(rect.x(), rect.y() + verticalOffset, rect.width(), progressBarHeight);
923 paintInfo.context().clipRoundedRect(FloatRoundedRect(border, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius));
925 float upperGradientHeight = progressBarHeight / 2.;
926 RefPtr<Gradient> upperGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + upperGradientHeight - 1.5) });
927 upperGradient->addColorStop(0.0, Color(133, 133, 133, 188));
928 upperGradient->addColorStop(1.0, Color(18, 18, 18, 51));
929 context.setFillGradient(upperGradient.releaseNonNull());
931 context.fillRect(FloatRect(rect.x(), verticalRenderingPosition, rect.width(), upperGradientHeight));
933 const auto& renderProgress = downcast<RenderProgress>(renderer);
934 if (renderProgress.isDeterminate()) {
935 // 2) Draw the progress bar.
936 double position = clampTo(renderProgress.position(), 0.0, 1.0);
937 double barWidth = position * rect.width();
938 RefPtr<Gradient> barGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
939 barGradient->addColorStop(0.0, Color(195, 217, 247));
940 barGradient->addColorStop(0.45, Color(118, 164, 228));
941 barGradient->addColorStop(0.49, Color(118, 164, 228));
942 barGradient->addColorStop(0.51, Color(36, 114, 210));
943 barGradient->addColorStop(0.55, Color(36, 114, 210));
944 barGradient->addColorStop(1.0, Color(57, 142, 244));
945 context.setFillGradient(barGradient.releaseNonNull());
947 RefPtr<Gradient> barStrokeGradient = Gradient::create(Gradient::LinearData { FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1) });
948 barStrokeGradient->addColorStop(0.0, Color(95, 107, 183));
949 barStrokeGradient->addColorStop(0.5, Color(66, 106, 174, 240));
950 barStrokeGradient->addColorStop(1.0, Color(38, 104, 166));
951 context.setStrokeGradient(barStrokeGradient.releaseNonNull());
955 if (!renderProgress.style().isLeftToRightDirection())
956 left = rect.maxX() - barWidth;
957 FloatRect barRect(left + 0.25, verticalRenderingPosition + 0.25, std::max(barWidth - 0.5, 0.0), progressBarHeight - 0.5);
958 barPath.addRoundedRect(barRect, roundedCornerRadius);
959 context.drawPath(barPath);
965 #if ENABLE(DATALIST_ELEMENT)
966 IntSize RenderThemeIOS::sliderTickSize() const
968 // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
969 return IntSize(1, 3);
972 int RenderThemeIOS::sliderTickOffsetFromTrackCenter() const
974 // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
979 void RenderThemeIOS::adjustSearchFieldStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
981 RenderTheme::adjustSearchFieldStyle(selector, style, element);
986 if (!style.hasBorder())
989 RenderBox* box = element->renderBox();
993 adjustRoundBorderRadius(style, *box);
996 bool RenderThemeIOS::paintSearchFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
998 return paintTextFieldDecorations(box, paintInfo, rect);
1001 void RenderThemeIOS::adjustButtonStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
1003 RenderTheme::adjustButtonStyle(selector, style, element);
1005 // Set padding: 0 1.0em; on buttons.
1006 // CSSPrimitiveValue::computeLengthInt only needs the element's style to calculate em lengths.
1007 // Since the element might not be in a document, just pass nullptr for the root element style
1008 // and the render view.
1009 RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(1.0, CSSPrimitiveValue::CSS_EMS);
1010 int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, nullptr, nullptr, 1.0, false));
1011 style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
1016 RenderBox* box = element->renderBox();
1020 adjustRoundBorderRadius(style, *box);
1023 bool RenderThemeIOS::paintButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1025 return paintPushButtonDecorations(box, paintInfo, rect);
1028 bool RenderThemeIOS::paintPushButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1030 GraphicsContextStateSaver stateSaver(paintInfo.context());
1031 FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
1033 CGContextRef cgContext = paintInfo.context().platformContext();
1034 if (box.style().visitedDependentColor(CSSPropertyBackgroundColor).isDark())
1035 drawAxialGradient(cgContext, gradientWithName(ConvexGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1037 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1038 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(clip.x(), clip.maxY()), clip.location(), ExponentialInterpolation);
1043 void RenderThemeIOS::setButtonSize(RenderStyle& style) const
1045 // If the width and height are both specified, then we have nothing to do.
1046 if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
1049 // Use the font size to determine the intrinsic width of the control.
1050 style.setHeight(Length(static_cast<int>(ControlBaseHeight / ControlBaseFontSize * style.fontDescription().computedSize()), Fixed));
1053 const int kThumbnailBorderStrokeWidth = 1;
1054 const int kThumbnailBorderCornerRadius = 1;
1055 const int kVisibleBackgroundImageWidth = 1;
1056 const int kMultipleThumbnailShrinkSize = 2;
1058 bool RenderThemeIOS::paintFileUploadIconDecorations(const RenderObject&, const RenderObject& buttonRenderer, const PaintInfo& paintInfo, const IntRect& rect, Icon* icon, FileUploadDecorations fileUploadDecorations)
1060 GraphicsContextStateSaver stateSaver(paintInfo.context());
1062 IntSize cornerSize(kThumbnailBorderCornerRadius, kThumbnailBorderCornerRadius);
1063 Color pictureFrameColor = buttonRenderer.style().visitedDependentColor(CSSPropertyBorderTopColor);
1065 IntRect thumbnailPictureFrameRect = rect;
1066 IntRect thumbnailRect = rect;
1067 thumbnailRect.contract(2 * kThumbnailBorderStrokeWidth, 2 * kThumbnailBorderStrokeWidth);
1068 thumbnailRect.move(kThumbnailBorderStrokeWidth, kThumbnailBorderStrokeWidth);
1070 if (fileUploadDecorations == MultipleFiles) {
1071 // Smaller thumbnails for multiple selection appearance.
1072 thumbnailPictureFrameRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1073 thumbnailRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1075 // Background picture frame and simple background icon with a gradient matching the button.
1076 Color backgroundImageColor = Color(buttonRenderer.style().visitedDependentColor(CSSPropertyBackgroundColor).rgb());
1077 paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1078 paintInfo.context().fillRect(thumbnailRect, backgroundImageColor);
1080 GraphicsContextStateSaver stateSaver2(paintInfo.context());
1081 CGContextRef cgContext = paintInfo.context().platformContext();
1082 paintInfo.context().clip(thumbnailRect);
1083 if (backgroundImageColor.isDark())
1084 drawAxialGradient(cgContext, gradientWithName(ConvexGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1086 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1087 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), thumbnailRect.location(), ExponentialInterpolation);
1091 // Move the rects for the Foreground picture frame and icon.
1092 int inset = kVisibleBackgroundImageWidth + kThumbnailBorderStrokeWidth;
1093 thumbnailPictureFrameRect.move(inset, inset);
1094 thumbnailRect.move(inset, inset);
1097 // Foreground picture frame and icon.
1098 paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1099 icon->paint(paintInfo.context(), thumbnailRect);
1104 Color RenderThemeIOS::platformActiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1106 return Color::transparent;
1109 Color RenderThemeIOS::platformInactiveSelectionBackgroundColor(OptionSet<StyleColor::Options>) const
1111 return Color::transparent;
1114 bool RenderThemeIOS::shouldHaveSpinButton(const HTMLInputElement&) const
1119 bool RenderThemeIOS::shouldHaveCapsLockIndicator(const HTMLInputElement&) const
1124 FontCascadeDescription& RenderThemeIOS::cachedSystemFontDescription(CSSValueID valueID) const
1126 static NeverDestroyed<FontCascadeDescription> systemFont;
1127 static NeverDestroyed<FontCascadeDescription> headlineFont;
1128 static NeverDestroyed<FontCascadeDescription> bodyFont;
1129 static NeverDestroyed<FontCascadeDescription> subheadlineFont;
1130 static NeverDestroyed<FontCascadeDescription> footnoteFont;
1131 static NeverDestroyed<FontCascadeDescription> caption1Font;
1132 static NeverDestroyed<FontCascadeDescription> caption2Font;
1133 static NeverDestroyed<FontCascadeDescription> shortHeadlineFont;
1134 static NeverDestroyed<FontCascadeDescription> shortBodyFont;
1135 static NeverDestroyed<FontCascadeDescription> shortSubheadlineFont;
1136 static NeverDestroyed<FontCascadeDescription> shortFootnoteFont;
1137 static NeverDestroyed<FontCascadeDescription> shortCaption1Font;
1138 static NeverDestroyed<FontCascadeDescription> tallBodyFont;
1139 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1140 static NeverDestroyed<FontCascadeDescription> title0Font;
1142 static NeverDestroyed<FontCascadeDescription> title1Font;
1143 static NeverDestroyed<FontCascadeDescription> title2Font;
1144 static NeverDestroyed<FontCascadeDescription> title3Font;
1145 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1146 static NeverDestroyed<FontCascadeDescription> title4Font;
1149 static CFStringRef userTextSize = contentSizeCategory();
1151 if (userTextSize != contentSizeCategory()) {
1152 userTextSize = contentSizeCategory();
1154 headlineFont.get().setIsAbsoluteSize(false);
1155 bodyFont.get().setIsAbsoluteSize(false);
1156 subheadlineFont.get().setIsAbsoluteSize(false);
1157 footnoteFont.get().setIsAbsoluteSize(false);
1158 caption1Font.get().setIsAbsoluteSize(false);
1159 caption2Font.get().setIsAbsoluteSize(false);
1160 shortHeadlineFont.get().setIsAbsoluteSize(false);
1161 shortBodyFont.get().setIsAbsoluteSize(false);
1162 shortSubheadlineFont.get().setIsAbsoluteSize(false);
1163 shortFootnoteFont.get().setIsAbsoluteSize(false);
1164 shortCaption1Font.get().setIsAbsoluteSize(false);
1165 tallBodyFont.get().setIsAbsoluteSize(false);
1169 case CSSValueAppleSystemHeadline:
1170 return headlineFont;
1171 case CSSValueAppleSystemBody:
1173 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1174 case CSSValueAppleSystemTitle0:
1177 case CSSValueAppleSystemTitle1:
1179 case CSSValueAppleSystemTitle2:
1181 case CSSValueAppleSystemTitle3:
1183 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1184 case CSSValueAppleSystemTitle4:
1187 case CSSValueAppleSystemSubheadline:
1188 return subheadlineFont;
1189 case CSSValueAppleSystemFootnote:
1190 return footnoteFont;
1191 case CSSValueAppleSystemCaption1:
1192 return caption1Font;
1193 case CSSValueAppleSystemCaption2:
1194 return caption2Font;
1196 case CSSValueAppleSystemShortHeadline:
1197 return shortHeadlineFont;
1198 case CSSValueAppleSystemShortBody:
1199 return shortBodyFont;
1200 case CSSValueAppleSystemShortSubheadline:
1201 return shortSubheadlineFont;
1202 case CSSValueAppleSystemShortFootnote:
1203 return shortFootnoteFont;
1204 case CSSValueAppleSystemShortCaption1:
1205 return shortCaption1Font;
1207 case CSSValueAppleSystemTallBody:
1208 return tallBodyFont;
1214 static inline FontSelectionValue cssWeightOfSystemFont(CTFontRef font)
1216 RetainPtr<CFDictionaryRef> traits = adoptCF(CTFontCopyTraits(font));
1217 CFNumberRef resultRef = (CFNumberRef)CFDictionaryGetValue(traits.get(), kCTFontWeightTrait);
1219 CFNumberGetValue(resultRef, kCFNumberFloatType, &result);
1220 // These numbers were experimentally gathered from weights of the system font.
1221 static float weightThresholds[] = { -0.6, -0.365, -0.115, 0.130, 0.235, 0.350, 0.5, 0.7 };
1222 for (unsigned i = 0; i < WTF_ARRAY_LENGTH(weightThresholds); ++i) {
1223 if (result < weightThresholds[i])
1224 return FontSelectionValue((static_cast<int>(i) + 1) * 100);
1226 return FontSelectionValue(900);
1229 void RenderThemeIOS::updateCachedSystemFontDescription(CSSValueID valueID, FontCascadeDescription& fontDescription) const
1231 RetainPtr<CTFontDescriptorRef> fontDescriptor;
1232 CFStringRef textStyle;
1234 case CSSValueAppleSystemHeadline:
1235 textStyle = kCTUIFontTextStyleHeadline;
1236 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1238 case CSSValueAppleSystemBody:
1239 textStyle = kCTUIFontTextStyleBody;
1240 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1242 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1243 case CSSValueAppleSystemTitle0:
1244 textStyle = kCTUIFontTextStyleTitle0;
1245 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1248 case CSSValueAppleSystemTitle1:
1249 textStyle = kCTUIFontTextStyleTitle1;
1250 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1252 case CSSValueAppleSystemTitle2:
1253 textStyle = kCTUIFontTextStyleTitle2;
1254 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1256 case CSSValueAppleSystemTitle3:
1257 textStyle = kCTUIFontTextStyleTitle3;
1258 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1260 #if __IPHONE_OS_VERSION_MIN_REQUIRED >= 110000
1261 case CSSValueAppleSystemTitle4:
1262 textStyle = kCTUIFontTextStyleTitle4;
1263 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1266 case CSSValueAppleSystemSubheadline:
1267 textStyle = kCTUIFontTextStyleSubhead;
1268 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1270 case CSSValueAppleSystemFootnote:
1271 textStyle = kCTUIFontTextStyleFootnote;
1272 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1274 case CSSValueAppleSystemCaption1:
1275 textStyle = kCTUIFontTextStyleCaption1;
1276 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1278 case CSSValueAppleSystemCaption2:
1279 textStyle = kCTUIFontTextStyleCaption2;
1280 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1284 case CSSValueAppleSystemShortHeadline:
1285 textStyle = kCTUIFontTextStyleShortHeadline;
1286 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1288 case CSSValueAppleSystemShortBody:
1289 textStyle = kCTUIFontTextStyleShortBody;
1290 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1292 case CSSValueAppleSystemShortSubheadline:
1293 textStyle = kCTUIFontTextStyleShortSubhead;
1294 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1296 case CSSValueAppleSystemShortFootnote:
1297 textStyle = kCTUIFontTextStyleShortFootnote;
1298 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1300 case CSSValueAppleSystemShortCaption1:
1301 textStyle = kCTUIFontTextStyleShortCaption1;
1302 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1306 case CSSValueAppleSystemTallBody:
1307 textStyle = kCTUIFontTextStyleTallBody;
1308 fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), nullptr));
1312 textStyle = kCTFontDescriptorTextStyleEmphasized;
1313 fontDescriptor = adoptCF(CTFontDescriptorCreateForUIType(kCTFontUIFontSystem, 0, nullptr));
1316 ASSERT(fontDescriptor);
1317 RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1318 fontDescription.setIsAbsoluteSize(true);
1319 fontDescription.setOneFamily(textStyle);
1320 fontDescription.setSpecifiedSize(CTFontGetSize(font.get()));
1321 fontDescription.setWeight(cssWeightOfSystemFont(font.get()));
1322 fontDescription.setItalic(normalItalicValue());
1326 String RenderThemeIOS::mediaControlsStyleSheet()
1328 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1329 if (m_legacyMediaControlsStyleSheet.isEmpty())
1330 m_legacyMediaControlsStyleSheet = [NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"css"] encoding:NSUTF8StringEncoding error:nil];
1331 return m_legacyMediaControlsStyleSheet;
1333 return emptyString();
1337 String RenderThemeIOS::modernMediaControlsStyleSheet()
1339 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1340 if (RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled()) {
1341 if (m_mediaControlsStyleSheet.isEmpty())
1342 m_mediaControlsStyleSheet = [NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"modern-media-controls" ofType:@"css" inDirectory:@"modern-media-controls"] encoding:NSUTF8StringEncoding error:nil];
1343 return m_mediaControlsStyleSheet;
1345 return emptyString();
1347 return emptyString();
1351 void RenderThemeIOS::purgeCaches()
1353 m_legacyMediaControlsScript.clearImplIfNotShared();
1354 m_mediaControlsScript.clearImplIfNotShared();
1355 m_legacyMediaControlsStyleSheet.clearImplIfNotShared();
1356 m_mediaControlsStyleSheet.clearImplIfNotShared();
1359 String RenderThemeIOS::mediaControlsScript()
1361 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1362 if (RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled()) {
1363 if (m_mediaControlsScript.isEmpty()) {
1364 NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1366 StringBuilder scriptBuilder;
1367 scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"modern-media-controls-localized-strings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1368 scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"modern-media-controls" ofType:@"js" inDirectory:@"modern-media-controls"] encoding:NSUTF8StringEncoding error:nil]);
1369 m_mediaControlsScript = scriptBuilder.toString();
1371 return m_mediaControlsScript;
1374 if (m_legacyMediaControlsScript.isEmpty()) {
1375 NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1377 StringBuilder scriptBuilder;
1378 scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsLocalizedStrings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1379 scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsApple" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1380 scriptBuilder.append([NSString stringWithContentsOfFile:[bundle pathForResource:@"mediaControlsiOS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1382 m_legacyMediaControlsScript = scriptBuilder.toString();
1384 return m_legacyMediaControlsScript;
1386 return emptyString();
1390 String RenderThemeIOS::mediaControlsBase64StringForIconNameAndType(const String& iconName, const String& iconType)
1392 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1393 if (!RuntimeEnabledFeatures::sharedFeatures().modernMediaControlsEnabled())
1394 return emptyString();
1396 String directory = "modern-media-controls/images";
1397 NSBundle *bundle = [NSBundle bundleForClass:[WebCoreRenderThemeBundle class]];
1398 return [[NSData dataWithContentsOfFile:[bundle pathForResource:iconName ofType:iconType inDirectory:directory]] base64EncodedStringWithOptions:0];
1400 return emptyString();
1404 #endif // ENABLE(VIDEO)
1406 Color RenderThemeIOS::systemColor(CSSValueID cssValueID, OptionSet<StyleColor::Options> options) const
1408 const bool forVisitedLink = options.contains(StyleColor::Options::ForVisitedLink);
1410 // The system color cache below can't handle visited links. The only color value
1411 // that cares about visited links is CSSValueWebkitLink, so handle it here by
1412 // calling through to RenderTheme's base implementation.
1413 if (forVisitedLink && cssValueID == CSSValueWebkitLink)
1414 return RenderTheme::systemColor(cssValueID, options);
1416 ASSERT(!forVisitedLink);
1418 auto addResult = m_systemColorCache.add(cssValueID, Color());
1419 if (!addResult.isNewEntry)
1420 return addResult.iterator->value;
1423 switch (cssValueID) {
1424 case CSSValueAppleWirelessPlaybackTargetActive:
1425 color = [getUIColorClass() systemBlueColor].CGColor;
1427 case CSSValueAppleSystemBlue:
1428 color = [getUIColorClass() systemBlueColor].CGColor;
1430 case CSSValueAppleSystemGray:
1431 color = [getUIColorClass() systemGrayColor].CGColor;
1433 case CSSValueAppleSystemGreen:
1434 color = [getUIColorClass() systemGreenColor].CGColor;
1436 case CSSValueAppleSystemOrange:
1437 color = [getUIColorClass() systemOrangeColor].CGColor;
1439 case CSSValueAppleSystemPink:
1440 color = [getUIColorClass() systemPinkColor].CGColor;
1442 case CSSValueAppleSystemRed:
1443 color = [getUIColorClass() systemRedColor].CGColor;
1445 case CSSValueAppleSystemYellow:
1446 color = [getUIColorClass() systemYellowColor].CGColor;
1452 if (!color.isValid())
1453 color = RenderTheme::systemColor(cssValueID, options);
1455 addResult.iterator->value = color;
1457 return addResult.iterator->value;
1460 #if ENABLE(ATTACHMENT_ELEMENT)
1462 const CGSize attachmentSize = { 160, 119 };
1464 const CGFloat attachmentBorderRadius = 16;
1465 static Color attachmentBorderColor() { return Color(204, 204, 204); }
1467 static Color attachmentProgressColor() { return Color(222, 222, 222); }
1468 const CGFloat attachmentProgressBorderThickness = 3;
1470 const CGFloat attachmentProgressSize = 36;
1471 const CGFloat attachmentIconSize = 48;
1473 const CGFloat attachmentItemMargin = 8;
1475 const CGFloat attachmentWrappingTextMaximumWidth = 140;
1476 const CFIndex attachmentWrappingTextMaximumLineCount = 2;
1478 static RetainPtr<CTFontRef> attachmentActionFont()
1480 RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortFootnote, RenderThemeIOS::contentSizeCategory(), 0));
1481 RetainPtr<CTFontDescriptorRef> emphasizedFontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithAttributes(fontDescriptor.get(),
1483 (id)kCTFontDescriptorTextStyleAttribute: (id)kCTFontDescriptorTextStyleEmphasized
1485 return adoptCF(CTFontCreateWithFontDescriptor(emphasizedFontDescriptor.get(), 0, nullptr));
1488 static UIColor *attachmentActionColor(const RenderAttachment& attachment)
1490 return [getUIColorClass() colorWithCGColor:cachedCGColor(attachment.style().visitedDependentColor(CSSPropertyColor))];
1493 static RetainPtr<CTFontRef> attachmentTitleFont()
1495 RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortCaption1, RenderThemeIOS::contentSizeCategory(), 0));
1496 return adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1499 static UIColor *attachmentTitleColor() { return [getUIColorClass() systemGrayColor]; }
1501 static RetainPtr<CTFontRef> attachmentSubtitleFont() { return attachmentTitleFont(); }
1502 static UIColor *attachmentSubtitleColor() { return [getUIColorClass() systemGrayColor]; }
1504 struct RenderAttachmentInfo {
1505 explicit RenderAttachmentInfo(const RenderAttachment&);
1508 FloatRect attachmentRect;
1509 FloatRect progressRect;
1511 BOOL hasProgress { NO };
1514 RetainPtr<UIImage> icon;
1520 RetainPtr<CTLineRef> line;
1522 Vector<LabelLine> lines;
1524 CGFloat contentYOrigin { 0 };
1527 void buildWrappedLines(const String&, CTFontRef, UIColor *, unsigned maximumLineCount);
1528 void buildSingleLine(const String&, CTFontRef, UIColor *);
1530 void addLine(CTLineRef);
1533 void RenderAttachmentInfo::addLine(CTLineRef line)
1535 CGRect lineBounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);
1536 CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
1537 CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
1538 CGFloat lineHeight = CGCeiling(lineBounds.size.height + lineBounds.origin.y);
1540 CGFloat xOffset = (attachmentRect.width() / 2) - (lineWidthIgnoringTrailingWhitespace / 2);
1541 LabelLine labelLine;
1542 labelLine.line = line;
1543 labelLine.rect = FloatRect(xOffset, 0, lineWidthIgnoringTrailingWhitespace, lineHeight);
1545 lines.append(labelLine);
1548 void RenderAttachmentInfo::buildWrappedLines(const String& text, CTFontRef font, UIColor *color, unsigned maximumLineCount)
1553 NSDictionary *textAttributes = @{
1554 (id)kCTFontAttributeName: (id)font,
1555 (id)kCTForegroundColorAttributeName: color
1557 RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1558 RetainPtr<CTFramesetterRef> framesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get()));
1561 CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter.get(), CFRangeMake(0, 0), nullptr, CGSizeMake(attachmentWrappingTextMaximumWidth, CGFLOAT_MAX), &fitRange);
1563 RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr));
1564 RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(framesetter.get(), fitRange, textPath.get(), nullptr));
1566 CFArrayRef ctLines = CTFrameGetLines(textFrame.get());
1567 CFIndex lineCount = CFArrayGetCount(ctLines);
1571 // Lay out and record the first (maximumLineCount - 1) lines.
1572 CFIndex lineIndex = 0;
1573 CFIndex nonTruncatedLineCount = std::min<CFIndex>(maximumLineCount - 1, lineCount);
1574 for (; lineIndex < nonTruncatedLineCount; ++lineIndex)
1575 addLine((CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex));
1577 if (lineIndex == lineCount)
1580 // We had text that didn't fit in the first (maximumLineCount - 1) lines.
1581 // Combine it into one last line, and center-truncate it.
1582 CTLineRef firstRemainingLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
1583 CFIndex remainingRangeStart = CTLineGetStringRange(firstRemainingLine).location;
1584 NSRange remainingRange = NSMakeRange(remainingRangeStart, [attributedText length] - remainingRangeStart);
1585 NSAttributedString *remainingString = [attributedText attributedSubstringFromRange:remainingRange];
1586 RetainPtr<CTLineRef> remainingLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)remainingString));
1587 RetainPtr<NSAttributedString> ellipsisString = adoptNS([[NSAttributedString alloc] initWithString:@"\u2026" attributes:textAttributes]);
1588 RetainPtr<CTLineRef> ellipsisLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)ellipsisString.get()));
1589 RetainPtr<CTLineRef> truncatedLine = adoptCF(CTLineCreateTruncatedLine(remainingLine.get(), attachmentWrappingTextMaximumWidth, kCTLineTruncationMiddle, ellipsisLine.get()));
1592 truncatedLine = remainingLine;
1594 addLine(truncatedLine.get());
1597 void RenderAttachmentInfo::buildSingleLine(const String& text, CTFontRef font, UIColor *color)
1602 NSDictionary *textAttributes = @{
1603 (id)kCTFontAttributeName: (id)font,
1604 (id)kCTForegroundColorAttributeName: color
1606 RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1608 addLine(adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)attributedText.get())).get());
1611 static BOOL getAttachmentProgress(const RenderAttachment& attachment, float& progress)
1613 auto& progressString = attachment.attachmentElement().attributeWithoutSynchronization(progressAttr);
1614 if (progressString.isEmpty())
1617 progress = std::max<float>(std::min<float>(progressString.toFloat(&validProgress), 1), 0);
1618 return validProgress;
1621 static RetainPtr<UIImage> iconForAttachment(const RenderAttachment& attachment, FloatSize& size)
1623 auto documentInteractionController = adoptNS([allocUIDocumentInteractionControllerInstance() init]);
1626 if (File* file = attachment.attachmentElement().file())
1627 fileName = file->name();
1629 if (fileName.isEmpty())
1630 fileName = attachment.attachmentElement().attachmentTitle();
1631 [documentInteractionController setName:fileName];
1633 String attachmentType = attachment.attachmentElement().attachmentType();
1634 if (!attachmentType.isEmpty()) {
1636 if (isDeclaredUTI(attachmentType))
1637 UTI = attachmentType;
1639 UTI = UTIFromMIMEType(attachmentType);
1641 [documentInteractionController setUTI:static_cast<NSString *>(UTI)];
1644 NSArray *icons = [documentInteractionController icons];
1648 RetainPtr<UIImage> result = icons.lastObject;
1650 BOOL useHeightForClosestMatch = [result size].height > [result size].width;
1651 CGFloat bestMatchRatio = -1;
1653 for (UIImage *icon in icons) {
1654 CGFloat iconSize = useHeightForClosestMatch ? icon.size.height : icon.size.width;
1656 CGFloat matchRatio = (attachmentIconSize / iconSize) - 1.0f;
1657 if (matchRatio < 0.3f) {
1658 matchRatio = CGFAbs(matchRatio);
1659 if ((bestMatchRatio == -1) || (matchRatio < bestMatchRatio)) {
1661 bestMatchRatio = matchRatio;
1666 CGFloat iconAspect = [result size].width / [result size].height;
1667 size = largestRectWithAspectRatioInsideRect(iconAspect, FloatRect(0, 0, attachmentIconSize, attachmentIconSize)).size();
1672 RenderAttachmentInfo::RenderAttachmentInfo(const RenderAttachment& attachment)
1674 attachmentRect = FloatRect(0, 0, attachment.width().toFloat(), attachment.height().toFloat());
1676 hasProgress = getAttachmentProgress(attachment, progress);
1678 String title = attachment.attachmentElement().attachmentTitle();
1679 String action = attachment.attachmentElement().attributeWithoutSynchronization(actionAttr);
1680 String subtitle = attachment.attachmentElement().attributeWithoutSynchronization(subtitleAttr);
1682 CGFloat yOffset = 0;
1685 progressRect = FloatRect((attachmentRect.width() / 2) - (attachmentProgressSize / 2), 0, attachmentProgressSize, attachmentProgressSize);
1686 yOffset += attachmentProgressSize + attachmentItemMargin;
1689 if (action.isEmpty() && !hasProgress) {
1691 icon = iconForAttachment(attachment, iconSize);
1693 iconRect = FloatRect(FloatPoint((attachmentRect.width() / 2) - (iconSize.width() / 2), 0), iconSize);
1694 yOffset += iconRect.height() + attachmentItemMargin;
1697 buildWrappedLines(action, attachmentActionFont().get(), attachmentActionColor(attachment), attachmentWrappingTextMaximumLineCount);
1699 bool forceSingleLineTitle = !action.isEmpty() || !subtitle.isEmpty() || hasProgress;
1700 buildWrappedLines(title, attachmentTitleFont().get(), attachmentTitleColor(), forceSingleLineTitle ? 1 : attachmentWrappingTextMaximumLineCount);
1701 buildSingleLine(subtitle, attachmentSubtitleFont().get(), attachmentSubtitleColor());
1703 if (!lines.isEmpty()) {
1704 for (auto& line : lines) {
1705 line.rect.setY(yOffset);
1706 yOffset += line.rect.height() + attachmentItemMargin;
1710 yOffset -= attachmentItemMargin;
1712 contentYOrigin = (attachmentRect.height() / 2) - (yOffset / 2);
1715 LayoutSize RenderThemeIOS::attachmentIntrinsicSize(const RenderAttachment&) const
1717 return LayoutSize(FloatSize(attachmentSize));
1720 int RenderThemeIOS::attachmentBaseline(const RenderAttachment& attachment) const
1722 RenderAttachmentInfo info(attachment);
1723 return info.baseline;
1726 static void paintAttachmentIcon(GraphicsContext& context, RenderAttachmentInfo& info)
1731 RefPtr<Image> iconImage = BitmapImage::create([info.icon CGImage]);
1735 context.drawImage(*iconImage, info.iconRect);
1738 static void paintAttachmentText(GraphicsContext& context, RenderAttachmentInfo& info)
1740 for (const auto& line : info.lines) {
1741 GraphicsContextStateSaver saver(context);
1743 context.translate(toFloatSize(line.rect.minXMaxYCorner()));
1744 context.scale(FloatSize(1, -1));
1746 CGContextSetTextPosition(context.platformContext(), 0, 0);
1747 CTLineDraw(line.line.get(), context.platformContext());
1751 static void paintAttachmentProgress(GraphicsContext& context, RenderAttachmentInfo& info)
1753 GraphicsContextStateSaver saver(context);
1755 context.setStrokeThickness(attachmentProgressBorderThickness);
1756 context.setStrokeColor(attachmentProgressColor());
1757 context.setFillColor(attachmentProgressColor());
1758 context.strokeEllipse(info.progressRect);
1760 FloatPoint center = info.progressRect.center();
1763 progressPath.moveTo(center);
1764 progressPath.addLineTo(FloatPoint(center.x(), info.progressRect.y()));
1765 progressPath.addArc(center, info.progressRect.width() / 2, -M_PI_2, info.progress * 2 * M_PI - M_PI_2, 0);
1766 progressPath.closeSubpath();
1767 context.fillPath(progressPath);
1770 static Path attachmentBorderPath(RenderAttachmentInfo& info)
1773 borderPath.addRoundedRect(info.attachmentRect, FloatSize(attachmentBorderRadius, attachmentBorderRadius));
1777 static void paintAttachmentBorder(GraphicsContext& context, Path& borderPath)
1779 context.setStrokeColor(attachmentBorderColor());
1780 context.setStrokeThickness(1);
1781 context.strokePath(borderPath);
1784 bool RenderThemeIOS::paintAttachment(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& paintRect)
1786 if (!is<RenderAttachment>(renderer))
1789 const RenderAttachment& attachment = downcast<RenderAttachment>(renderer);
1791 RenderAttachmentInfo info(attachment);
1793 GraphicsContext& context = paintInfo.context();
1794 GraphicsContextStateSaver saver(context);
1796 context.translate(toFloatSize(paintRect.location()));
1798 if (attachment.shouldDrawBorder()) {
1799 auto borderPath = attachmentBorderPath(info);
1800 paintAttachmentBorder(context, borderPath);
1801 context.clipPath(borderPath);
1804 context.translate(FloatSize(0, info.contentYOrigin));
1806 if (info.hasProgress)
1807 paintAttachmentProgress(context, info);
1809 paintAttachmentIcon(context, info);
1811 paintAttachmentText(context, info);
1816 #endif // ENABLE(ATTACHMENT_ELEMENT)
1818 #if PLATFORM(WATCHOS)
1820 String RenderThemeIOS::extraDefaultStyleSheet()
1822 return ASCIILiteral("* { -webkit-text-size-adjust: auto; -webkit-hyphens: auto !important; }");
1827 #if USE(SYSTEM_PREVIEW)
1828 void RenderThemeIOS::paintSystemPreviewBadge(Image& image, const PaintInfo& paintInfo, const FloatRect& rect)
1830 static const int largeBadgeDimension = 70;
1831 static const int largeBadgeOffset = 20;
1833 static const int smallBadgeDimension = 35;
1834 static const int smallBadgeOffset = 8;
1836 static const int minimumSizeForLargeBadge = 240;
1838 bool useSmallBadge = rect.width() < minimumSizeForLargeBadge || rect.height() < minimumSizeForLargeBadge;
1839 int badgeOffset = useSmallBadge ? smallBadgeOffset : largeBadgeOffset;
1840 int badgeDimension = useSmallBadge ? smallBadgeDimension : largeBadgeDimension;
1842 int minimumDimension = badgeDimension + 2 * badgeOffset;
1843 if (rect.width() < minimumDimension || rect.height() < minimumDimension)
1846 CGRect absoluteBadgeRect = CGRectMake(rect.x() + rect.width() - badgeDimension - badgeOffset, rect.y() + badgeOffset, badgeDimension, badgeDimension);
1847 CGRect insetBadgeRect = CGRectMake(rect.width() - badgeDimension - badgeOffset, badgeOffset, badgeDimension, badgeDimension);
1848 CGRect badgeRect = CGRectMake(0, 0, badgeDimension, badgeDimension);
1850 CIImage *inputImage = [CIImage imageWithCGImage:image.nativeImage().get()];
1852 // Create a circle to be used for the clipping path in the badge, as well as the drop shadow.
1853 RetainPtr<CGPathRef> circle = adoptCF(CGPathCreateWithRoundedRect(absoluteBadgeRect, badgeDimension / 2, badgeDimension / 2, nullptr));
1855 auto& graphicsContext = paintInfo.context();
1856 if (graphicsContext.paintingDisabled())
1859 CGContextRef ctx = graphicsContext.platformContext();
1863 CGContextSaveGState(ctx);
1865 // Draw a drop shadow around the circle.
1866 CGFloat shadowColorComponents[4] = { 0, 0, 0, 0.1 };
1867 RetainPtr<CGColorRef> shadowColor = adoptCF(CGColorCreate(sRGBColorSpaceRef(), shadowColorComponents));
1868 // The circle must have an alpha channel value of 1 for the shadow color to appear.
1869 CGFloat circleColorComponents[4] = { 0, 0, 0, 1 };
1870 RetainPtr<CGColorRef> circleColor = adoptCF(CGColorCreate(sRGBColorSpaceRef(), circleColorComponents));
1871 CGContextSetFillColorWithColor(ctx, circleColor.get());
1872 CGContextSetShadowWithColor(ctx, CGSizeZero, 16, shadowColor.get());
1874 // Clip out the circle to only show the shadow.
1875 CGContextBeginPath(ctx);
1876 CGContextAddRect(ctx, rect);
1877 CGContextAddPath(ctx, circle.get());
1878 CGContextClosePath(ctx);
1879 CGContextEOClip(ctx);
1881 // Draw a slightly smaller circle with a shadow, otherwise we'll see a fringe of the solid
1882 // black circle around the edges of the clipped path below.
1883 CGContextBeginPath(ctx);
1884 CGRect slightlySmallerAbsoluteBadgeRect = CGRectMake(absoluteBadgeRect.origin.x + 0.5, absoluteBadgeRect.origin.y + 0.5, badgeDimension - 1, badgeDimension - 1);
1885 RetainPtr<CGPathRef> slightlySmallerCircle = adoptCF(CGPathCreateWithRoundedRect(slightlySmallerAbsoluteBadgeRect, slightlySmallerAbsoluteBadgeRect.size.width / 2, slightlySmallerAbsoluteBadgeRect.size.height / 2, nullptr));
1886 CGContextAddPath(ctx, slightlySmallerCircle.get());
1887 CGContextClosePath(ctx);
1888 CGContextFillPath(ctx);
1890 CGContextRestoreGState(ctx);
1892 // Draw the blurred backdrop. Scale from intrinsic size to render size.
1893 CGAffineTransform transform = CGAffineTransformIdentity;
1894 transform = CGAffineTransformScale(transform, rect.width() / image.width(), rect.height() / image.height());
1895 CIImage *scaledImage = [inputImage imageByApplyingTransform:transform];
1897 // CoreImage coordinates are y-up, so we need to flip the badge rectangle within the image frame.
1898 CGRect flippedInsetBadgeRect = CGRectMake(insetBadgeRect.origin.x, rect.height() - insetBadgeRect.origin.y - insetBadgeRect.size.height, badgeDimension, badgeDimension);
1900 // Create a cropped region with pixel values extending outwards.
1901 CIImage *clampedImage = [scaledImage imageByClampingToRect:flippedInsetBadgeRect];
1904 CIImage *blurredImage = [clampedImage imageByApplyingGaussianBlurWithSigma:10];
1907 CIFilter *saturationFilter = [CIFilter filterWithName:@"CIColorControls"];
1908 [saturationFilter setValue:blurredImage forKey:kCIInputImageKey];
1909 [saturationFilter setValue:@1.8 forKey:kCIInputSaturationKey];
1912 CIFilter *tintFilter1 = [CIFilter filterWithName:@"CIConstantColorGenerator"];
1913 CIColor *tintColor1 = [CIColor colorWithRed:1 green:1 blue:1 alpha:0.18];
1914 [tintFilter1 setValue:tintColor1 forKey:kCIInputColorKey];
1916 // Blend the tint with the saturated output.
1917 CIFilter *sourceOverFilter = [CIFilter filterWithName:@"CISourceOverCompositing"];
1918 [sourceOverFilter setValue:tintFilter1.outputImage forKey:kCIInputImageKey];
1919 [sourceOverFilter setValue:saturationFilter.outputImage forKey:kCIInputBackgroundImageKey];
1922 m_ciContext = [CIContext context];
1924 RetainPtr<CGImageRef> cgImage;
1926 // Crop the result to the badge location.
1927 CIImage *croppedImage = [sourceOverFilter.outputImage imageByCroppingToRect:flippedInsetBadgeRect];
1928 CIImage *translatedImage = [croppedImage imageByApplyingTransform:CGAffineTransformMakeTranslation(-flippedInsetBadgeRect.origin.x, -flippedInsetBadgeRect.origin.y)];
1929 IOSurfaceRef surface;
1930 if (useSmallBadge) {
1931 if (!m_smallBadgeSurface)
1932 m_smallBadgeSurface = IOSurface::create({ smallBadgeDimension, smallBadgeDimension }, sRGBColorSpaceRef());
1933 surface = m_smallBadgeSurface->surface();
1935 if (!m_largeBadgeSurface)
1936 m_largeBadgeSurface = IOSurface::create({ largeBadgeDimension, largeBadgeDimension }, sRGBColorSpaceRef());
1937 surface = m_largeBadgeSurface->surface();
1939 [m_ciContext.get() render:translatedImage toIOSurface:surface bounds:badgeRect colorSpace:sRGBColorSpaceRef()];
1940 cgImage = useSmallBadge ? m_smallBadgeSurface->createImage() : m_largeBadgeSurface->createImage();
1942 cgImage = adoptCF([m_ciContext.get() createCGImage:sourceOverFilter.outputImage fromRect:flippedInsetBadgeRect]);
1945 // Before we render the result, we should clip to a circle around the badge rectangle.
1946 CGContextSaveGState(ctx);
1947 CGContextBeginPath(ctx);
1948 CGContextAddPath(ctx, circle.get());
1949 CGContextClosePath(ctx);
1952 CGContextTranslateCTM(ctx, absoluteBadgeRect.origin.x, absoluteBadgeRect.origin.y);
1953 CGContextTranslateCTM(ctx, 0, badgeDimension);
1954 CGContextScaleCTM(ctx, 1, -1);
1955 CGContextDrawImage(ctx, badgeRect, cgImage.get());
1957 #if USE(APPLE_INTERNAL_SDK)
1958 if (auto logo = systemPreviewLogo()) {
1959 CGSize pdfSize = CGPDFPageGetBoxRect(logo, kCGPDFMediaBox).size;
1960 CGFloat scaleX = badgeDimension / pdfSize.width;
1961 CGFloat scaleY = badgeDimension / pdfSize.height;
1962 CGContextScaleCTM(ctx, scaleX, scaleY);
1963 CGContextDrawPDFPage(ctx, logo);
1967 CGContextFlush(ctx);
1968 CGContextRestoreGState(ctx);
1972 } // namespace WebCore
1974 #endif //PLATFORM(IOS)