e11e69f8530023d5e46d6f0da348ac9028c0ed4a
[WebKit-https.git] / Source / WebCore / rendering / RenderThemeIOS.mm
1 /*
2  * Copyright (C) 2005, 2006, 2007, 2008 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. 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.
24  */
25
26 #import "config.h"
27
28 #if PLATFORM(IOS)
29
30 #import "BitmapImage.h"
31 #import "CSSPrimitiveValue.h"
32 #import "CSSToLengthConversionData.h"
33 #import "CSSValueKeywords.h"
34 #import "CoreTextSPI.h"
35 #import "DateComponents.h"
36 #import "Document.h"
37 #import "File.h"
38 #import "FloatRoundedRect.h"
39 #import "FontCache.h"
40 #import "FontCascade.h"
41 #import "Frame.h"
42 #import "FrameSelection.h"
43 #import "FrameView.h"
44 #import "GeometryUtilities.h"
45 #import "Gradient.h"
46 #import "GraphicsContext.h"
47 #import "GraphicsContextCG.h"
48 #import "HTMLAttachmentElement.h"
49 #import "HTMLInputElement.h"
50 #import "HTMLNames.h"
51 #import "HTMLSelectElement.h"
52 #import "Icon.h"
53 #import "LocalizedDateCache.h"
54 #import "NodeRenderStyle.h"
55 #import "Page.h"
56 #import "PaintInfo.h"
57 #import "PathUtilities.h"
58 #import "PlatformLocale.h"
59 #import "RenderAttachment.h"
60 #import "RenderObject.h"
61 #import "RenderProgress.h"
62 #import "RenderStyle.h"
63 #import "RenderThemeIOS.h"
64 #import "RenderView.h"
65 #import "SoftLinking.h"
66 #import "UIKitSPI.h"
67 #import "UTIUtilities.h"
68 #import "UserAgentScripts.h"
69 #import "UserAgentStyleSheets.h"
70 #import "WebCoreThreadRun.h"
71 #import <CoreGraphics/CoreGraphics.h>
72 #import <objc/runtime.h>
73 #import <wtf/NeverDestroyed.h>
74 #import <wtf/RefPtr.h>
75 #import <wtf/StdLibExtras.h>
76
77 SOFT_LINK_FRAMEWORK(UIKit)
78 SOFT_LINK_CLASS(UIKit, UIApplication)
79 SOFT_LINK_CLASS(UIKit, UIColor)
80 SOFT_LINK_CLASS(UIKit, UIDocumentInteractionController)
81 SOFT_LINK_CLASS(UIKit, UIImage)
82 SOFT_LINK_CONSTANT(UIKit, UIContentSizeCategoryDidChangeNotification, CFStringRef)
83 #define UIContentSizeCategoryDidChangeNotification getUIContentSizeCategoryDidChangeNotification()
84
85 @interface WebCoreRenderThemeBundle : NSObject
86 @end
87
88 @implementation WebCoreRenderThemeBundle
89 @end
90
91 namespace WebCore {
92
93 using namespace HTMLNames;
94
95 const float ControlBaseHeight = 20;
96 const float ControlBaseFontSize = 11;
97
98 struct IOSGradient {
99     float* start; // points to static float[4]
100     float* end; // points to static float[4]
101     IOSGradient(float start[4], float end[4])
102         : start(start)
103         , end(end)
104     {
105     }
106 };
107
108 typedef IOSGradient* IOSGradientRef;
109
110 enum Interpolation
111 {
112     LinearInterpolation,
113     ExponentialInterpolation
114 };
115
116 static void interpolateLinearGradient(void *info, const CGFloat *inData, CGFloat *outData)
117 {
118     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
119     float alpha = inData[0];
120     float inverse = 1.0f - alpha;
121
122     outData[0] = inverse * gradient->start[0] + alpha * gradient->end[0];
123     outData[1] = inverse * gradient->start[1] + alpha * gradient->end[1];
124     outData[2] = inverse * gradient->start[2] + alpha * gradient->end[2];
125     outData[3] = inverse * gradient->start[3] + alpha * gradient->end[3];
126 }
127
128 static void interpolateExponentialGradient(void *info, const CGFloat *inData, CGFloat *outData)
129 {
130     IOSGradientRef gradient = static_cast<IOSGradientRef>(info);
131     float a = inData[0];
132     for (int paintInfo = 0; paintInfo < 4; ++paintInfo) {
133         float end = logf(std::max(gradient->end[paintInfo], 0.01f));
134         float start = logf(std::max(gradient->start[paintInfo], 0.01f));
135         outData[paintInfo] = expf(start - (end + start) * a);
136     }
137 }
138
139 static CGFunctionRef getSharedFunctionRef(IOSGradientRef gradient, Interpolation interpolation)
140 {
141     CGFunctionRef function = nullptr;
142
143     static HashMap<IOSGradientRef, CGFunctionRef>* linearFunctionRefs;
144     static HashMap<IOSGradientRef, CGFunctionRef>* exponentialFunctionRefs;
145
146     if (interpolation == LinearInterpolation) {
147         if (!linearFunctionRefs)
148             linearFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
149         else
150             function = linearFunctionRefs->get(gradient);
151     
152         if (!function) {
153             static struct CGFunctionCallbacks linearFunctionCallbacks =  { 0, interpolateLinearGradient, 0 };
154             linearFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, nullptr, 4, nullptr, &linearFunctionCallbacks));
155         }
156
157         return function;
158     }
159
160     if (!exponentialFunctionRefs)
161         exponentialFunctionRefs = new HashMap<IOSGradientRef, CGFunctionRef>;
162     else
163         function = exponentialFunctionRefs->get(gradient);
164
165     if (!function) {
166         static struct CGFunctionCallbacks exponentialFunctionCallbacks =  { 0, interpolateExponentialGradient, 0 };
167         exponentialFunctionRefs->set(gradient, function = CGFunctionCreate(gradient, 1, 0, 4, 0, &exponentialFunctionCallbacks));
168     }
169
170     return function;
171 }
172
173 static void drawAxialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, const FloatPoint& stopPoint, Interpolation interpolation)
174 {
175     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateAxial(sRGBColorSpaceRef(), startPoint, stopPoint, getSharedFunctionRef(gradient, interpolation), false, false));
176     CGContextDrawShading(context, shading.get());
177 }
178
179 static void drawRadialGradient(CGContextRef context, IOSGradientRef gradient, const FloatPoint& startPoint, float startRadius, const FloatPoint& stopPoint, float stopRadius, Interpolation interpolation)
180 {
181     RetainPtr<CGShadingRef> shading = adoptCF(CGShadingCreateRadial(sRGBColorSpaceRef(), startPoint, startRadius, stopPoint, stopRadius, getSharedFunctionRef(gradient, interpolation), false, false));
182     CGContextDrawShading(context, shading.get());
183 }
184
185 enum IOSGradientType {
186     InsetGradient,
187     ShineGradient,
188     ShadeGradient,
189     ConvexGradient,
190     ConcaveGradient,
191     SliderTrackGradient,
192     ReadonlySliderTrackGradient,
193     SliderThumbOpaquePressedGradient,
194 };
195
196 static IOSGradientRef getInsetGradient()
197 {
198     static float end[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0 };
199     static float start[4] = { 0 / 255.0, 0 / 255.0, 0 / 255.0, 0.2 };
200     static NeverDestroyed<IOSGradient> gradient(start, end);
201     return &gradient.get();
202 }
203
204 static IOSGradientRef getShineGradient()
205 {
206     static float end[4] = { 1, 1, 1, 0.8 };
207     static float start[4] = { 1, 1, 1, 0 };
208     static NeverDestroyed<IOSGradient> gradient(start, end);
209     return &gradient.get();
210 }
211
212 static IOSGradientRef getShadeGradient()
213 {
214     static float end[4] = { 178 / 255.0, 178 / 255.0, 178 / 255.0, 0.65 };
215     static float start[4] = { 252 / 255.0, 252 / 255.0, 252 / 255.0, 0.65 };
216     static NeverDestroyed<IOSGradient> gradient(start, end);
217     return &gradient.get();
218 }
219
220 static IOSGradientRef getConvexGradient()
221 {
222     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.05 };
223     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.43 };
224     static NeverDestroyed<IOSGradient> gradient(start, end);
225     return &gradient.get();
226 }
227
228 static IOSGradientRef getConcaveGradient()
229 {
230     static float end[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0.46 };
231     static float start[4] = { 255 / 255.0, 255 / 255.0, 255 / 255.0, 0 };
232     static NeverDestroyed<IOSGradient> gradient(start, end);
233     return &gradient.get();
234 }
235
236 static IOSGradientRef getSliderTrackGradient()
237 {
238     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 1 };
239     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 / 255.0, 1 };
240     static NeverDestroyed<IOSGradient> gradient(start, end);
241     return &gradient.get();
242 }
243
244 static IOSGradientRef getReadonlySliderTrackGradient()
245 {
246     static float end[4] = { 132 / 255.0, 132 / 255.0, 132 / 255.0, 0.4 };
247     static float start[4] = { 74 / 255.0, 77 / 255.0, 80 /255.0, 0.4 };
248     static NeverDestroyed<IOSGradient> gradient(start, end);
249     return &gradient.get();
250 }
251
252 static IOSGradientRef getSliderThumbOpaquePressedGradient()
253 {
254     static float end[4] = { 144 / 255.0, 144 / 255.0, 144 / 255.0, 1};
255     static float start[4] = { 55 / 255.0, 55 / 255.0, 55 / 255.0, 1 };
256     static NeverDestroyed<IOSGradient> gradient(start, end);
257     return &gradient.get();
258 }
259
260 static IOSGradientRef gradientWithName(IOSGradientType gradientType)
261 {
262     switch (gradientType) {
263     case InsetGradient:
264         return getInsetGradient();
265     case ShineGradient:
266         return getShineGradient();
267     case ShadeGradient:
268         return getShadeGradient();
269     case ConvexGradient:
270         return getConvexGradient();
271     case ConcaveGradient:
272         return getConcaveGradient();
273     case SliderTrackGradient:
274         return getSliderTrackGradient();
275     case ReadonlySliderTrackGradient:
276         return getReadonlySliderTrackGradient();
277     case SliderThumbOpaquePressedGradient:
278         return getSliderThumbOpaquePressedGradient();
279     }
280     ASSERT_NOT_REACHED();
281     return nullptr;
282 }
283
284 static void contentSizeCategoryDidChange(CFNotificationCenterRef, void*, CFStringRef name, const void*, CFDictionaryRef)
285 {
286     ASSERT_UNUSED(name, CFEqual(name, UIContentSizeCategoryDidChangeNotification));
287     WebThreadRun(^{
288         Page::updateStyleForAllPagesAfterGlobalChangeInEnvironment();
289     });
290 }
291
292 RenderThemeIOS::RenderThemeIOS()
293 {
294     CFNotificationCenterAddObserver(CFNotificationCenterGetLocalCenter(), this, contentSizeCategoryDidChange, UIContentSizeCategoryDidChangeNotification, 0, CFNotificationSuspensionBehaviorDeliverImmediately);
295 }
296
297 PassRefPtr<RenderTheme> RenderTheme::themeForPage(Page*)
298 {
299     static RenderTheme& renderTheme = RenderThemeIOS::create().leakRef();
300     return &renderTheme;
301 }
302
303 Ref<RenderTheme> RenderThemeIOS::create()
304 {
305     return adoptRef(*new RenderThemeIOS);
306 }
307
308 static String& _contentSizeCategory()
309 {
310     static NeverDestroyed<String> _contentSizeCategory;
311     return _contentSizeCategory.get();
312 }
313
314 CFStringRef RenderThemeIOS::contentSizeCategory()
315 {
316     if (!_contentSizeCategory().isNull())
317         return (__bridge CFStringRef)static_cast<NSString*>(_contentSizeCategory());
318     return (CFStringRef)[[getUIApplicationClass() sharedApplication] preferredContentSizeCategory];
319 }
320
321 void RenderThemeIOS::setContentSizeCategory(const String& contentSizeCategory)
322 {
323     _contentSizeCategory() = contentSizeCategory;
324 }
325
326 const Color& RenderThemeIOS::shadowColor() const
327 {
328     static Color color(0.0f, 0.0f, 0.0f, 0.7f);
329     return color;
330 }
331
332 FloatRect RenderThemeIOS::addRoundedBorderClip(const RenderObject& box, GraphicsContext& context, const IntRect& rect)
333 {
334     // To fix inner border bleeding issues <rdar://problem/9812507>, we clip to the outer border and assert that
335     // the border is opaque or transparent, unless we're checked because checked radio/checkboxes show no bleeding.
336     auto& style = box.style();
337     RoundedRect border = isChecked(box) ? style.getRoundedInnerBorderFor(rect) : style.getRoundedBorderFor(rect);
338
339     if (border.isRounded())
340         context.clipRoundedRect(FloatRoundedRect(border));
341     else
342         context.clip(border.rect());
343
344     if (isChecked(box)) {
345         ASSERT(style.visitedDependentColor(CSSPropertyBorderTopColor).alpha() % 255 == 0);
346         ASSERT(style.visitedDependentColor(CSSPropertyBorderRightColor).alpha() % 255 == 0);
347         ASSERT(style.visitedDependentColor(CSSPropertyBorderBottomColor).alpha() % 255 == 0);
348         ASSERT(style.visitedDependentColor(CSSPropertyBorderLeftColor).alpha() % 255 == 0);
349     }
350
351     return border.rect();
352 }
353
354 void RenderThemeIOS::adjustCheckboxStyle(StyleResolver&, RenderStyle& style, const Element*) const
355 {
356     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
357         return;
358
359     Length length = Length(static_cast<int>(ceilf(std::max(style.fontSize(), 10))), Fixed);
360     
361     style.setWidth(length);
362     style.setHeight(length);
363 }
364
365 static CGPoint shortened(CGPoint start, CGPoint end, float width)
366 {
367     float x = end.x - start.x;
368     float y = end.y - start.y;
369     float ratio = (!x && !y) ? 0 : width / sqrtf(x * x + y * y);
370     return CGPointMake(start.x + x * ratio, start.y + y * ratio);
371 }
372
373 static void drawJoinedLines(CGContextRef context, CGPoint points[], unsigned count, bool antialias, CGLineCap lineCap)
374 {
375     CGContextSetShouldAntialias(context, antialias);
376     CGContextBeginPath(context);
377     CGContextSetLineCap(context, lineCap);
378     CGContextMoveToPoint(context, points[0].x, points[0].y);
379     
380     for (unsigned i = 1; i < count; ++i)
381         CGContextAddLineToPoint(context, points[i].x, points[i].y);
382
383     CGContextStrokePath(context);
384 }
385
386 bool RenderThemeIOS::paintCheckboxDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
387 {
388     GraphicsContextStateSaver stateSaver(paintInfo.context());
389     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
390
391     float width = clip.width();
392     float height = clip.height();
393
394     CGContextRef cgContext = paintInfo.context().platformContext();
395     if (isChecked(box)) {
396         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
397
398         static const float thicknessRatio = 2 / 14.0;
399         static const CGSize size = { 14.0f, 14.0f };
400         static const CGPoint pathRatios[3] = {
401             { 2.5f / size.width, 7.5f / size.height },
402             { 5.5f / size.width, 10.5f / size.height },
403             { 11.5f / size.width, 2.5f / size.height }
404         };
405
406         float lineWidth = std::min(width, height) * 2.0f * thicknessRatio;
407
408         CGPoint line[3] = {
409             CGPointMake(clip.x() + width * pathRatios[0].x, clip.y() + height * pathRatios[0].y),
410             CGPointMake(clip.x() + width * pathRatios[1].x, clip.y() + height * pathRatios[1].y),
411             CGPointMake(clip.x() + width * pathRatios[2].x, clip.y() + height * pathRatios[2].y)
412         };
413         CGPoint shadow[3] = {
414             shortened(line[0], line[1], lineWidth / 4.0f),
415             line[1],
416             shortened(line[2], line[1], lineWidth / 4.0f)
417         };
418
419         lineWidth = std::max<float>(lineWidth, 1);
420         CGContextSetLineWidth(cgContext, lineWidth);
421         CGContextSetStrokeColorWithColor(cgContext, cachedCGColor(Color(0.0f, 0.0f, 0.0f, 0.7f)));
422         drawJoinedLines(cgContext, shadow, 3, true, kCGLineCapSquare);
423
424         lineWidth = std::max<float>(std::min(clip.width(), clip.height()) * thicknessRatio, 1);
425         CGContextSetLineWidth(cgContext, lineWidth);
426         CGContextSetStrokeColorWithColor(cgContext, cachedCGColor(Color(1.0f, 1.0f, 1.0f, 240 / 255.0f)));
427         drawJoinedLines(cgContext, line, 3, true, kCGLineCapButt);
428     } else {
429         FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
430         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
431         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, sqrtf((width * width) / 4.0f + height * height), ExponentialInterpolation);
432     }
433
434     return false;
435 }
436
437 int RenderThemeIOS::baselinePosition(const RenderBox& box) const
438 {
439     if (box.style().appearance() == CheckboxPart || box.style().appearance() == RadioPart)
440         return box.marginTop() + box.height() - 2; // The baseline is 2px up from the bottom of the checkbox/radio in AppKit.
441     if (box.style().appearance() == MenulistPart)
442         return box.marginTop() + box.height() - 5; // This is to match AppKit. There might be a better way to calculate this though.
443     return RenderTheme::baselinePosition(box);
444 }
445
446 bool RenderThemeIOS::isControlStyled(const RenderStyle& style, const BorderData& border, const FillLayer& background, const Color& backgroundColor) const
447 {
448     // Buttons and MenulistButtons are styled if they contain a background image.
449     if (style.appearance() == PushButtonPart || style.appearance() == MenulistButtonPart)
450         return !style.visitedDependentColor(CSSPropertyBackgroundColor).alpha() || style.backgroundLayers()->hasImage();
451
452     if (style.appearance() == TextFieldPart || style.appearance() == TextAreaPart)
453         return *style.backgroundLayers() != background;
454
455     return RenderTheme::isControlStyled(style, border, background, backgroundColor);
456 }
457
458 void RenderThemeIOS::adjustRadioStyle(StyleResolver&, RenderStyle& style, const Element*) const
459 {
460     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
461         return;
462
463     Length length = Length(static_cast<int>(ceilf(std::max(style.fontSize(), 10))), Fixed);
464     style.setWidth(length);
465     style.setHeight(length);
466     style.setBorderRadius(IntSize(length.value() / 2.0f, length.value() / 2.0f));
467 }
468
469 bool RenderThemeIOS::paintRadioDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
470 {
471     GraphicsContextStateSaver stateSaver(paintInfo.context());
472     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
473
474     CGContextRef cgContext = paintInfo.context().platformContext();
475     if (isChecked(box)) {
476         drawAxialGradient(cgContext, gradientWithName(ConcaveGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
477
478         // The inner circle is 6 / 14 the size of the surrounding circle, 
479         // leaving 8 / 14 around it. (8 / 14) / 2 = 2 / 7.
480
481         static const float InnerInverseRatio = 2 / 7.0;
482
483         clip.inflateX(-clip.width() * InnerInverseRatio);
484         clip.inflateY(-clip.height() * InnerInverseRatio);
485
486         paintInfo.context().drawRaisedEllipse(clip, Color::white, shadowColor());
487
488         FloatSize radius(clip.width() / 2.0f, clip.height() / 2.0f);
489         paintInfo.context().clipRoundedRect(FloatRoundedRect(clip, radius, radius, radius, radius));
490     }
491     FloatPoint bottomCenter(clip.x() + clip.width() / 2.0, clip.maxY());
492     drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
493     drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
494     return false;
495 }
496
497 bool RenderThemeIOS::paintTextFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
498 {
499     auto& style = box.style();
500     FloatPoint point(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth());
501
502     GraphicsContextStateSaver stateSaver(paintInfo.context());
503
504     paintInfo.context().clipRoundedRect(style.getRoundedBorderFor(LayoutRect(rect)).pixelSnappedRoundedRectForPainting(box.document().deviceScaleFactor()));
505
506     // This gradient gets drawn black when printing.
507     // Do not draw the gradient if there is no visible top border.
508     bool topBorderIsInvisible = !style.hasBorder() || !style.borderTopWidth() || style.borderTopIsTransparent();
509     if (!box.view().printing() && !topBorderIsInvisible)
510         drawAxialGradient(paintInfo.context().platformContext(), gradientWithName(InsetGradient), point, FloatPoint(CGPointMake(point.x(), point.y() + 3.0f)), LinearInterpolation);
511     return false;
512 }
513
514 bool RenderThemeIOS::paintTextAreaDecorations(const RenderObject& box, const PaintInfo& paintInfo, const FloatRect& rect)
515 {
516     return paintTextFieldDecorations(box, paintInfo, rect);
517 }
518
519 const int MenuListMinHeight = 15;
520
521 const float MenuListBaseHeight = 20;
522 const float MenuListBaseFontSize = 11;
523
524 const float MenuListArrowWidth = 7;
525 const float MenuListArrowHeight = 6;
526 const float MenuListButtonPaddingAfter = 19;
527
528 LengthBox RenderThemeIOS::popupInternalPaddingBox(const RenderStyle& style) const
529 {
530     if (style.appearance() == MenulistButtonPart) {
531         if (style.direction() == RTL)
532             return { 0, 0, 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()) };
533         return { 0, static_cast<int>(MenuListButtonPaddingAfter + style.borderTopWidth()), 0, 0 };
534     }
535     return { 0, 0, 0, 0 };
536 }
537
538 void RenderThemeIOS::adjustRoundBorderRadius(RenderStyle& style, RenderBox& box)
539 {
540     if (style.appearance() == NoControlPart || style.backgroundLayers()->hasImage())
541         return;
542
543     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>
544     Length radiusWidth(static_cast<int>(std::min(box.width(), box.height()) / 2.0), Fixed);
545     Length radiusHeight(static_cast<int>(box.height() / 2.0), Fixed);
546     style.setBorderRadius(LengthSize(radiusWidth, radiusHeight));
547 }
548
549 static void applyCommonButtonPaddingToStyle(RenderStyle& style, const Element& element)
550 {
551     Document& document = element.document();
552     RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(0.5, CSSPrimitiveValue::CSS_EMS);
553     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, document.renderStyle(), document.renderView(), document.frame()->pageZoomFactor()));
554     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
555 }
556
557 static void adjustSelectListButtonStyle(RenderStyle& style, const Element& element)
558 {
559     // Enforce "padding: 0 0.5em".
560     applyCommonButtonPaddingToStyle(style, element);
561
562     // Enforce "line-height: normal".
563     style.setLineHeight(Length(-100.0, Percent));
564 }
565     
566 class RenderThemeMeasureTextClient : public MeasureTextClient {
567 public:
568     RenderThemeMeasureTextClient(const FontCascade& font, const RenderStyle& style)
569         : m_font(font)
570         , m_style(style)
571     {
572     }
573     float measureText(const String& string) const override
574     {
575         TextRun run = RenderBlock::constructTextRun(string, m_style);
576         return m_font.width(run);
577     }
578 private:
579     const FontCascade& m_font;
580     const RenderStyle& m_style;
581 };
582
583 static void adjustInputElementButtonStyle(RenderStyle& style, const HTMLInputElement& inputElement)
584 {
585     // Always Enforce "padding: 0 0.5em".
586     applyCommonButtonPaddingToStyle(style, inputElement);
587
588     // Don't adjust the style if the width is specified.
589     if (style.width().isFixed() && style.width().value() > 0)
590         return;
591
592     // Don't adjust for unsupported date input types.
593     DateComponents::Type dateType = inputElement.dateType();
594     if (dateType == DateComponents::Invalid || dateType == DateComponents::Week)
595         return;
596
597     // Enforce the width and set the box-sizing to content-box to not conflict with the padding.
598     FontCascade font = style.fontCascade();
599     
600     float maximumWidth = localizedDateCache().maximumWidthForDateType(dateType, font, RenderThemeMeasureTextClient(font, style));
601
602     ASSERT(maximumWidth >= 0);
603
604     if (maximumWidth > 0) {
605         int width = static_cast<int>(maximumWidth + MenuListButtonPaddingAfter);
606         style.setWidth(Length(width, Fixed));
607         style.setBoxSizing(CONTENT_BOX);
608     }
609 }
610
611 void RenderThemeIOS::adjustMenuListButtonStyle(StyleResolver&, RenderStyle& style, const Element* element) const
612 {
613     // Set the min-height to be at least MenuListMinHeight.
614     if (style.height().isAuto())
615         style.setMinHeight(Length(std::max(MenuListMinHeight, static_cast<int>(MenuListBaseHeight / MenuListBaseFontSize * style.fontDescription().computedSize())), Fixed));
616     else
617         style.setMinHeight(Length(MenuListMinHeight, Fixed));
618
619     if (!element)
620         return;
621
622     // Enforce some default styles in the case that this is a non-multiple <select> element,
623     // or a date input. We don't force these if this is just an element with
624     // "-webkit-appearance: menulist-button".
625     if (is<HTMLSelectElement>(*element) && !element->hasAttributeWithoutSynchronization(HTMLNames::multipleAttr))
626         adjustSelectListButtonStyle(style, *element);
627     else if (is<HTMLInputElement>(*element))
628         adjustInputElementButtonStyle(style, downcast<HTMLInputElement>(*element));
629 }
630
631 bool RenderThemeIOS::paintMenuListButtonDecorations(const RenderBox& box, const PaintInfo& paintInfo, const FloatRect& rect)
632 {
633     auto& style = box.style();
634     bool isRTL = style.direction() == RTL;
635     float borderTopWidth = style.borderTopWidth();
636     FloatRect clip(rect.x() + style.borderLeftWidth(), rect.y() + style.borderTopWidth(), rect.width() - style.borderLeftWidth() - style.borderRightWidth(), rect.height() - style.borderTopWidth() - style.borderBottomWidth());
637     CGContextRef cgContext = paintInfo.context().platformContext();
638
639     float adjustLeft = 0.5;
640     float adjustRight = 0.5;
641     float adjustTop = 0.5;
642     float adjustBottom = 0.5;
643
644     // Paint title portion.
645     {
646         float leftInset = isRTL ? MenuListButtonPaddingAfter : 0;
647         FloatRect titleClip(clip.x() + leftInset - adjustLeft, clip.y() - adjustTop, clip.width() - MenuListButtonPaddingAfter + adjustLeft, clip.height() + adjustTop + adjustBottom);
648
649         GraphicsContextStateSaver stateSaver(paintInfo.context());
650
651         FloatSize topLeftRadius;
652         FloatSize topRightRadius;
653         FloatSize bottomLeftRadius;
654         FloatSize bottomRightRadius;
655
656         if (isRTL) {
657             topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height(), rect.height()) - style.borderTopWidth());
658             bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height(), rect.height()) - style.borderBottomWidth());
659         } else {
660             topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height(), rect.height()) - style.borderTopWidth());
661             bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height(), rect.height()) - style.borderBottomWidth());
662         }
663
664         paintInfo.context().clipRoundedRect(FloatRoundedRect(titleClip,
665             topLeftRadius, topRightRadius,
666             bottomLeftRadius, bottomRightRadius));
667
668         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), titleClip.location(), FloatPoint(titleClip.x(), titleClip.maxY()), LinearInterpolation);
669         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(titleClip.x(), titleClip.maxY()), titleClip.location(), ExponentialInterpolation);
670     }
671
672     // Draw the separator after the initial padding.
673
674     float separatorPosition = isRTL ? (clip.x() + MenuListButtonPaddingAfter) : (clip.maxX() - MenuListButtonPaddingAfter);
675
676     box.drawLineForBoxSide(paintInfo.context(), FloatRect(FloatPoint(separatorPosition - borderTopWidth, clip.y()), FloatPoint(separatorPosition, clip.maxY())), BSRight, style.visitedDependentColor(CSSPropertyBorderTopColor), style.borderTopStyle(), 0, 0);
677
678     FloatRect buttonClip;
679     if (isRTL)
680         buttonClip = FloatRect(clip.x() - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustLeft, clip.height() + adjustTop + adjustBottom);
681     else
682         buttonClip = FloatRect(separatorPosition - adjustTop, clip.y() - adjustTop, MenuListButtonPaddingAfter + adjustTop + adjustRight, clip.height() + adjustTop + adjustBottom);
683
684     // Now paint the button portion.
685     {
686         GraphicsContextStateSaver stateSaver(paintInfo.context());
687
688         FloatSize topLeftRadius;
689         FloatSize topRightRadius;
690         FloatSize bottomLeftRadius;
691         FloatSize bottomRightRadius;
692
693         if (isRTL) {
694             topLeftRadius = FloatSize(valueForLength(style.borderTopLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderTopLeftRadius().height(), rect.height()) - style.borderTopWidth());
695             bottomLeftRadius = FloatSize(valueForLength(style.borderBottomLeftRadius().width(), rect.width()) - style.borderLeftWidth(), valueForLength(style.borderBottomLeftRadius().height(), rect.height()) - style.borderBottomWidth());
696         } else {
697             topRightRadius = FloatSize(valueForLength(style.borderTopRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderTopRightRadius().height(), rect.height()) - style.borderTopWidth());
698             bottomRightRadius = FloatSize(valueForLength(style.borderBottomRightRadius().width(), rect.width()) - style.borderRightWidth(), valueForLength(style.borderBottomRightRadius().height(), rect.height()) - style.borderBottomWidth());
699         }
700
701         paintInfo.context().clipRoundedRect(FloatRoundedRect(buttonClip,
702             topLeftRadius, topRightRadius,
703             bottomLeftRadius, bottomRightRadius));
704
705         paintInfo.context().fillRect(buttonClip, style.visitedDependentColor(CSSPropertyBorderTopColor));
706
707         drawAxialGradient(cgContext, gradientWithName(isFocused(box) && !isReadOnlyControl(box) ? ConcaveGradient : ConvexGradient), buttonClip.location(), FloatPoint(buttonClip.x(), buttonClip.maxY()), LinearInterpolation);
708     }
709
710     // Paint Indicators.
711
712     if (box.isMenuList() && downcast<HTMLSelectElement>(box.element())->multiple()) {
713         int size = 2;
714         int count = 3;
715         int padding = 3;
716
717         FloatRect ellipse(buttonClip.x() + (buttonClip.width() - count * (size + padding) + padding) / 2.0, buttonClip.maxY() - 10.0, size, size);
718
719         for (int i = 0; i < count; ++i) {
720             paintInfo.context().drawRaisedEllipse(ellipse, Color::white, Color(0.0f, 0.0f, 0.0f, 0.5f));
721             ellipse.move(size + padding, 0);
722         }
723     }  else {
724         float centerX = floorf(buttonClip.x() + buttonClip.width() / 2.0) - 0.5;
725         float centerY = floorf(buttonClip.y() + buttonClip.height() * 3.0 / 8.0);
726
727         Vector<FloatPoint> arrow = {
728             { centerX - MenuListArrowWidth / 2, centerY },
729             { centerX + MenuListArrowWidth / 2, centerY },
730             { centerX, centerY + MenuListArrowHeight }
731         };
732
733         Vector<FloatPoint> shadow = {
734             { arrow[0].x(), arrow[0].y() + 1 },
735             { arrow[1].x(), arrow[1].y() + 1 },
736             { arrow[2].x(), arrow[2].y() + 1 }
737         };
738
739         float opacity = isReadOnlyControl(box) ? 0.2 : 0.5;
740         paintInfo.context().setStrokeColor(Color(0.0f, 0.0f, 0.0f, opacity));
741         paintInfo.context().setFillColor(Color(0.0f, 0.0f, 0.0f, opacity));
742         paintInfo.context().drawPath(Path::polygonPathFromPoints(shadow));
743
744         paintInfo.context().setStrokeColor(Color::white);
745         paintInfo.context().setFillColor(Color::white);
746         paintInfo.context().drawPath(Path::polygonPathFromPoints(arrow));
747     }
748
749     return false;
750 }
751
752 const CGFloat kTrackThickness = 4.0;
753 const CGFloat kTrackRadius = kTrackThickness / 2.0;
754 const int kDefaultSliderThumbSize = 16;
755
756 void RenderThemeIOS::adjustSliderTrackStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
757 {
758     RenderTheme::adjustSliderTrackStyle(selector, style, element);
759
760     // FIXME: We should not be relying on border radius for the appearance of our controls <rdar://problem/7675493>
761     Length radiusWidth(static_cast<int>(kTrackRadius), Fixed);
762     Length radiusHeight(static_cast<int>(kTrackRadius), Fixed);
763     style.setBorderRadius(LengthSize(radiusWidth, radiusHeight));
764 }
765
766 bool RenderThemeIOS::paintSliderTrack(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
767 {
768     IntRect trackClip = rect;
769     auto& style = box.style();
770
771     bool isHorizontal = true;
772     switch (style.appearance()) {
773     case SliderHorizontalPart:
774         isHorizontal = true;
775         // Inset slightly so the thumb covers the edge.
776         if (trackClip.width() > 2) {
777             trackClip.setWidth(trackClip.width() - 2);
778             trackClip.setX(trackClip.x() + 1);
779         }
780         trackClip.setHeight(static_cast<int>(kTrackThickness));
781         trackClip.setY(rect.y() + rect.height() / 2 - kTrackThickness / 2);
782         break;
783     case SliderVerticalPart:
784         isHorizontal = false;
785         // Inset slightly so the thumb covers the edge.
786         if (trackClip.height() > 2) {
787             trackClip.setHeight(trackClip.height() - 2);
788             trackClip.setY(trackClip.y() + 1);
789         }
790         trackClip.setWidth(kTrackThickness);
791         trackClip.setX(rect.x() + rect.width() / 2 - kTrackThickness / 2);
792         break;
793     default:
794         ASSERT_NOT_REACHED();
795     }
796
797     ASSERT(trackClip.width() >= 0);
798     ASSERT(trackClip.height() >= 0);
799     CGFloat cornerWidth = trackClip.width() < kTrackThickness ? trackClip.width() / 2.0f : kTrackRadius;
800     CGFloat cornerHeight = trackClip.height() < kTrackThickness ? trackClip.height() / 2.0f : kTrackRadius;
801
802     bool readonly = isReadOnlyControl(box);
803
804 #if ENABLE(DATALIST_ELEMENT)
805     paintSliderTicks(box, paintInfo, trackClip);
806 #endif
807
808     // Draw the track gradient.
809     {
810         GraphicsContextStateSaver stateSaver(paintInfo.context());
811
812         IntSize cornerSize(cornerWidth, cornerHeight);
813         FloatRoundedRect innerBorder(trackClip, cornerSize, cornerSize, cornerSize, cornerSize);
814         paintInfo.context().clipRoundedRect(innerBorder);
815
816         CGContextRef cgContext = paintInfo.context().platformContext();
817         IOSGradientRef gradient = readonly ? gradientWithName(ReadonlySliderTrackGradient) : gradientWithName(SliderTrackGradient);
818         if (isHorizontal)
819             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.x(), trackClip.maxY()), LinearInterpolation);
820         else
821             drawAxialGradient(cgContext, gradient, trackClip.location(), FloatPoint(trackClip.maxX(), trackClip.y()), LinearInterpolation);
822     }
823
824     // Draw the track border.
825     {
826         GraphicsContextStateSaver stateSaver(paintInfo.context());
827
828         CGContextRef cgContext = paintInfo.context().platformContext();
829         if (readonly)
830             paintInfo.context().setStrokeColor(Color(178, 178, 178));
831         else
832             paintInfo.context().setStrokeColor(Color(76, 76, 76));
833
834         RetainPtr<CGMutablePathRef> roundedRectPath = adoptCF(CGPathCreateMutable());
835         CGPathAddRoundedRect(roundedRectPath.get(), 0, trackClip, cornerWidth, cornerHeight);
836         CGContextAddPath(cgContext, roundedRectPath.get());
837         CGContextSetLineWidth(cgContext, 1);
838         CGContextStrokePath(cgContext);
839     }
840
841     return false;
842 }
843
844 void RenderThemeIOS::adjustSliderThumbSize(RenderStyle& style, const Element*) const
845 {
846     if (style.appearance() != SliderThumbHorizontalPart && style.appearance() != SliderThumbVerticalPart)
847         return;
848
849     // Enforce "border-radius: 50%".
850     Length length(50.0f, Percent);
851     style.setBorderRadius(LengthSize(length, length));
852
853     // Enforce a 16x16 size if no size is provided.
854     if (style.width().isIntrinsicOrAuto() || style.height().isAuto()) {
855         Length length = Length(kDefaultSliderThumbSize, Fixed);
856         style.setWidth(length);
857         style.setHeight(length);
858     }
859 }
860
861 bool RenderThemeIOS::paintSliderThumbDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
862 {
863     GraphicsContextStateSaver stateSaver(paintInfo.context());
864     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
865
866     CGContextRef cgContext = paintInfo.context().platformContext();
867     FloatPoint bottomCenter(clip.x() + clip.width() / 2.0f, clip.maxY());
868     if (isPressed(box))
869         drawAxialGradient(cgContext, gradientWithName(SliderThumbOpaquePressedGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
870     else {
871         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
872         drawRadialGradient(cgContext, gradientWithName(ShineGradient), bottomCenter, 0.0f, bottomCenter, std::max(clip.width(), clip.height()), ExponentialInterpolation);
873     }
874
875     return false;
876 }
877
878 double RenderThemeIOS::animationRepeatIntervalForProgressBar(RenderProgress&) const
879 {
880     return 0;
881 }
882
883 double RenderThemeIOS::animationDurationForProgressBar(RenderProgress&) const
884 {
885     return 0;
886 }
887
888 bool RenderThemeIOS::paintProgressBar(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& rect)
889 {
890     if (!is<RenderProgress>(renderer))
891         return true;
892
893     const int progressBarHeight = 9;
894     const float verticalOffset = (rect.height() - progressBarHeight) / 2.0;
895
896     GraphicsContextStateSaver stateSaver(paintInfo.context());
897     if (rect.width() < 10 || rect.height() < 9) {
898         // The rect is smaller than the standard progress bar. We clip to the element's rect to avoid
899         // leaking pixels outside the repaint rect.
900         paintInfo.context().clip(rect);
901     }
902
903     // 1) Draw the progress bar track.
904     // 1.1) Draw the white background with grey gradient border.
905     GraphicsContext& context = paintInfo.context();
906     context.setStrokeThickness(0.68);
907     context.setStrokeStyle(SolidStroke);
908
909     const float verticalRenderingPosition = rect.y() + verticalOffset;
910     RefPtr<Gradient> strokeGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
911     strokeGradient->addColorStop(0.0, Color(0x8d, 0x8d, 0x8d));
912     strokeGradient->addColorStop(0.45, Color(0xee, 0xee, 0xee));
913     strokeGradient->addColorStop(0.55, Color(0xee, 0xee, 0xee));
914     strokeGradient->addColorStop(1.0, Color(0x8d, 0x8d, 0x8d));
915     context.setStrokeGradient(strokeGradient.releaseNonNull());
916
917     context.setFillColor(Color(255, 255, 255));
918
919     Path trackPath;
920     FloatRect trackRect(rect.x() + 0.25, verticalRenderingPosition + 0.25, rect.width() - 0.5, progressBarHeight - 0.5);
921     FloatSize roundedCornerRadius(5, 4);
922     trackPath.addRoundedRect(trackRect, roundedCornerRadius);
923     context.drawPath(trackPath);
924
925     // 1.2) Draw top gradient on the upper half. It is supposed to overlay the fill from the background and darker the stroked path.
926     FloatRect border(rect.x(), rect.y() + verticalOffset, rect.width(), progressBarHeight);
927     paintInfo.context().clipRoundedRect(FloatRoundedRect(border, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius, roundedCornerRadius));
928
929     float upperGradientHeight = progressBarHeight / 2.;
930     RefPtr<Gradient> upperGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + upperGradientHeight - 1.5));
931     upperGradient->addColorStop(0.0, Color(133, 133, 133, 188));
932     upperGradient->addColorStop(1.0, Color(18, 18, 18, 51));
933     context.setFillGradient(upperGradient.releaseNonNull());
934
935     context.fillRect(FloatRect(rect.x(), verticalRenderingPosition, rect.width(), upperGradientHeight));
936
937     const auto& renderProgress = downcast<RenderProgress>(renderer);
938     if (renderProgress.isDeterminate()) {
939         // 2) Draw the progress bar.
940         double position = clampTo(renderProgress.position(), 0.0, 1.0);
941         double barWidth = position * rect.width();
942         RefPtr<Gradient> barGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition + 0.5), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
943         barGradient->addColorStop(0.0, Color(195, 217, 247));
944         barGradient->addColorStop(0.45, Color(118, 164, 228));
945         barGradient->addColorStop(0.49, Color(118, 164, 228));
946         barGradient->addColorStop(0.51, Color(36, 114, 210));
947         barGradient->addColorStop(0.55, Color(36, 114, 210));
948         barGradient->addColorStop(1.0, Color(57, 142, 244));
949         context.setFillGradient(barGradient.releaseNonNull());
950
951         RefPtr<Gradient> barStrokeGradient = Gradient::create(FloatPoint(rect.x(), verticalRenderingPosition), FloatPoint(rect.x(), verticalRenderingPosition + progressBarHeight - 1));
952         barStrokeGradient->addColorStop(0.0, Color(95, 107, 183));
953         barStrokeGradient->addColorStop(0.5, Color(66, 106, 174, 240));
954         barStrokeGradient->addColorStop(1.0, Color(38, 104, 166));
955         context.setStrokeGradient(barStrokeGradient.releaseNonNull());
956
957         Path barPath;
958         int left = rect.x();
959         if (!renderProgress.style().isLeftToRightDirection())
960             left = rect.maxX() - barWidth;
961         FloatRect barRect(left + 0.25, verticalRenderingPosition + 0.25, std::max(barWidth - 0.5, 0.0), progressBarHeight - 0.5);
962         barPath.addRoundedRect(barRect, roundedCornerRadius);
963         context.drawPath(barPath);
964     }
965
966     return false;
967 }
968
969 #if ENABLE(DATALIST_ELEMENT)
970 IntSize RenderThemeIOS::sliderTickSize() const
971 {
972     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
973     return IntSize(1, 3);
974 }
975
976 int RenderThemeIOS::sliderTickOffsetFromTrackCenter() const
977 {
978     // FIXME: <rdar://problem/12271791> MERGEBOT: Correct values for slider tick of <input type="range"> elements (requires ENABLE_DATALIST_ELEMENT)
979     return -9;
980 }
981 #endif
982
983 void RenderThemeIOS::adjustSearchFieldStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
984 {
985     RenderTheme::adjustSearchFieldStyle(selector, style, element);
986
987     if (!element)
988         return;
989
990     if (!style.hasBorder())
991         return;
992
993     RenderBox* box = element->renderBox();
994     if (!box)
995         return;
996
997     adjustRoundBorderRadius(style, *box);
998 }
999
1000 bool RenderThemeIOS::paintSearchFieldDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1001 {
1002     return paintTextFieldDecorations(box, paintInfo, rect);
1003 }
1004
1005 void RenderThemeIOS::adjustButtonStyle(StyleResolver& selector, RenderStyle& style, const Element* element) const
1006 {
1007     RenderTheme::adjustButtonStyle(selector, style, element);
1008
1009     // Set padding: 0 1.0em; on buttons.
1010     // CSSPrimitiveValue::computeLengthInt only needs the element's style to calculate em lengths.
1011     // Since the element might not be in a document, just pass nullptr for the root element style
1012     // and the render view.
1013     RefPtr<CSSPrimitiveValue> emSize = CSSPrimitiveValue::create(1.0, CSSPrimitiveValue::CSS_EMS);
1014     int pixels = emSize->computeLength<int>(CSSToLengthConversionData(&style, nullptr, nullptr, 1.0, false));
1015     style.setPaddingBox(LengthBox(0, pixels, 0, pixels));
1016
1017     if (!element)
1018         return;
1019
1020     RenderBox* box = element->renderBox();
1021     if (!box)
1022         return;
1023
1024     adjustRoundBorderRadius(style, *box);
1025 }
1026
1027 bool RenderThemeIOS::paintButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1028 {
1029     return paintPushButtonDecorations(box, paintInfo, rect);
1030 }
1031
1032 bool RenderThemeIOS::paintPushButtonDecorations(const RenderObject& box, const PaintInfo& paintInfo, const IntRect& rect)
1033 {
1034     GraphicsContextStateSaver stateSaver(paintInfo.context());
1035     FloatRect clip = addRoundedBorderClip(box, paintInfo.context(), rect);
1036
1037     CGContextRef cgContext = paintInfo.context().platformContext();
1038     if (box.style().visitedDependentColor(CSSPropertyBackgroundColor).isDark())
1039         drawAxialGradient(cgContext, gradientWithName(ConvexGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1040     else {
1041         drawAxialGradient(cgContext, gradientWithName(ShadeGradient), clip.location(), FloatPoint(clip.x(), clip.maxY()), LinearInterpolation);
1042         drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(clip.x(), clip.maxY()), clip.location(), ExponentialInterpolation);
1043     }
1044     return false;
1045 }
1046
1047 void RenderThemeIOS::setButtonSize(RenderStyle& style) const
1048 {
1049     // If the width and height are both specified, then we have nothing to do.
1050     if (!style.width().isIntrinsicOrAuto() && !style.height().isAuto())
1051         return;
1052
1053     // Use the font size to determine the intrinsic width of the control.
1054     style.setHeight(Length(static_cast<int>(ControlBaseHeight / ControlBaseFontSize * style.fontDescription().computedSize()), Fixed));
1055 }
1056
1057 const int kThumbnailBorderStrokeWidth = 1;
1058 const int kThumbnailBorderCornerRadius = 1;
1059 const int kVisibleBackgroundImageWidth = 1;
1060 const int kMultipleThumbnailShrinkSize = 2;
1061
1062 bool RenderThemeIOS::paintFileUploadIconDecorations(const RenderObject&, const RenderObject& buttonRenderer, const PaintInfo& paintInfo, const IntRect& rect, Icon* icon, FileUploadDecorations fileUploadDecorations)
1063 {
1064     GraphicsContextStateSaver stateSaver(paintInfo.context());
1065
1066     IntSize cornerSize(kThumbnailBorderCornerRadius, kThumbnailBorderCornerRadius);
1067     Color pictureFrameColor = buttonRenderer.style().visitedDependentColor(CSSPropertyBorderTopColor);
1068
1069     IntRect thumbnailPictureFrameRect = rect;
1070     IntRect thumbnailRect = rect;
1071     thumbnailRect.contract(2 * kThumbnailBorderStrokeWidth, 2 * kThumbnailBorderStrokeWidth);
1072     thumbnailRect.move(kThumbnailBorderStrokeWidth, kThumbnailBorderStrokeWidth);
1073
1074     if (fileUploadDecorations == MultipleFiles) {
1075         // Smaller thumbnails for multiple selection appearance.
1076         thumbnailPictureFrameRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1077         thumbnailRect.contract(kMultipleThumbnailShrinkSize, kMultipleThumbnailShrinkSize);
1078
1079         // Background picture frame and simple background icon with a gradient matching the button.
1080         Color backgroundImageColor = Color(buttonRenderer.style().visitedDependentColor(CSSPropertyBackgroundColor).rgb());
1081         paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1082         paintInfo.context().fillRect(thumbnailRect, backgroundImageColor);
1083         {
1084             GraphicsContextStateSaver stateSaver2(paintInfo.context());
1085             CGContextRef cgContext = paintInfo.context().platformContext();
1086             paintInfo.context().clip(thumbnailRect);
1087             if (backgroundImageColor.isDark())
1088                 drawAxialGradient(cgContext, gradientWithName(ConvexGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1089             else {
1090                 drawAxialGradient(cgContext, gradientWithName(ShadeGradient), thumbnailRect.location(), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), LinearInterpolation);
1091                 drawAxialGradient(cgContext, gradientWithName(ShineGradient), FloatPoint(thumbnailRect.x(), thumbnailRect.maxY()), thumbnailRect.location(), ExponentialInterpolation);
1092             }
1093         }
1094
1095         // Move the rects for the Foreground picture frame and icon.
1096         int inset = kVisibleBackgroundImageWidth + kThumbnailBorderStrokeWidth;
1097         thumbnailPictureFrameRect.move(inset, inset);
1098         thumbnailRect.move(inset, inset);
1099     }
1100
1101     // Foreground picture frame and icon.
1102     paintInfo.context().fillRoundedRect(FloatRoundedRect(thumbnailPictureFrameRect, cornerSize, cornerSize, cornerSize, cornerSize), pictureFrameColor);
1103     icon->paint(paintInfo.context(), thumbnailRect);
1104
1105     return false;
1106 }
1107
1108 Color RenderThemeIOS::platformActiveSelectionBackgroundColor() const
1109 {
1110     return Color::transparent;
1111 }
1112
1113 Color RenderThemeIOS::platformInactiveSelectionBackgroundColor() const
1114 {
1115     return Color::transparent;
1116 }
1117
1118 bool RenderThemeIOS::shouldHaveSpinButton(const HTMLInputElement&) const
1119 {
1120     return false;
1121 }
1122
1123 bool RenderThemeIOS::shouldHaveCapsLockIndicator(const HTMLInputElement&) const
1124 {
1125     return false;
1126 }
1127
1128 FontCascadeDescription& RenderThemeIOS::cachedSystemFontDescription(CSSValueID valueID) const
1129 {
1130     static NeverDestroyed<FontCascadeDescription> systemFont;
1131     static NeverDestroyed<FontCascadeDescription> headlineFont;
1132     static NeverDestroyed<FontCascadeDescription> bodyFont;
1133     static NeverDestroyed<FontCascadeDescription> subheadlineFont;
1134     static NeverDestroyed<FontCascadeDescription> footnoteFont;
1135     static NeverDestroyed<FontCascadeDescription> caption1Font;
1136     static NeverDestroyed<FontCascadeDescription> caption2Font;
1137     static NeverDestroyed<FontCascadeDescription> shortHeadlineFont;
1138     static NeverDestroyed<FontCascadeDescription> shortBodyFont;
1139     static NeverDestroyed<FontCascadeDescription> shortSubheadlineFont;
1140     static NeverDestroyed<FontCascadeDescription> shortFootnoteFont;
1141     static NeverDestroyed<FontCascadeDescription> shortCaption1Font;
1142     static NeverDestroyed<FontCascadeDescription> tallBodyFont;
1143     static NeverDestroyed<FontCascadeDescription> title1Font;
1144     static NeverDestroyed<FontCascadeDescription> title2Font;
1145     static NeverDestroyed<FontCascadeDescription> title3Font;
1146
1147     static CFStringRef userTextSize = contentSizeCategory();
1148
1149     if (userTextSize != contentSizeCategory()) {
1150         userTextSize = contentSizeCategory();
1151
1152         headlineFont.get().setIsAbsoluteSize(false);
1153         bodyFont.get().setIsAbsoluteSize(false);
1154         subheadlineFont.get().setIsAbsoluteSize(false);
1155         footnoteFont.get().setIsAbsoluteSize(false);
1156         caption1Font.get().setIsAbsoluteSize(false);
1157         caption2Font.get().setIsAbsoluteSize(false);
1158         shortHeadlineFont.get().setIsAbsoluteSize(false);
1159         shortBodyFont.get().setIsAbsoluteSize(false);
1160         shortSubheadlineFont.get().setIsAbsoluteSize(false);
1161         shortFootnoteFont.get().setIsAbsoluteSize(false);
1162         shortCaption1Font.get().setIsAbsoluteSize(false);
1163         tallBodyFont.get().setIsAbsoluteSize(false);
1164     }
1165
1166     switch (valueID) {
1167     case CSSValueAppleSystemHeadline:
1168         return headlineFont;
1169     case CSSValueAppleSystemBody:
1170         return bodyFont;
1171     case CSSValueAppleSystemTitle1:
1172         return title1Font;
1173     case CSSValueAppleSystemTitle2:
1174         return title2Font;
1175     case CSSValueAppleSystemTitle3:
1176         return title3Font;
1177     case CSSValueAppleSystemSubheadline:
1178         return subheadlineFont;
1179     case CSSValueAppleSystemFootnote:
1180         return footnoteFont;
1181     case CSSValueAppleSystemCaption1:
1182         return caption1Font;
1183     case CSSValueAppleSystemCaption2:
1184         return caption2Font;
1185         // Short version.
1186     case CSSValueAppleSystemShortHeadline:
1187         return shortHeadlineFont;
1188     case CSSValueAppleSystemShortBody:
1189         return shortBodyFont;
1190     case CSSValueAppleSystemShortSubheadline:
1191         return shortSubheadlineFont;
1192     case CSSValueAppleSystemShortFootnote:
1193         return shortFootnoteFont;
1194     case CSSValueAppleSystemShortCaption1:
1195         return shortCaption1Font;
1196         // Tall version.
1197     case CSSValueAppleSystemTallBody:
1198         return tallBodyFont;
1199     default:
1200         return systemFont;
1201     }
1202 }
1203
1204 void RenderThemeIOS::updateCachedSystemFontDescription(CSSValueID valueID, FontCascadeDescription& fontDescription) const
1205 {
1206     RetainPtr<CTFontDescriptorRef> fontDescriptor;
1207     CFStringRef textStyle;
1208     switch (valueID) {
1209     case CSSValueAppleSystemHeadline:
1210         textStyle = kCTUIFontTextStyleHeadline;
1211         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1212         break;
1213     case CSSValueAppleSystemBody:
1214         textStyle = kCTUIFontTextStyleBody;
1215         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1216         break;
1217     case CSSValueAppleSystemTitle1:
1218         textStyle = kCTUIFontTextStyleTitle1;
1219         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1220         break;
1221     case CSSValueAppleSystemTitle2:
1222         textStyle = kCTUIFontTextStyleTitle2;
1223         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1224         break;
1225     case CSSValueAppleSystemTitle3:
1226         textStyle = kCTUIFontTextStyleTitle3;
1227         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1228         break;
1229     case CSSValueAppleSystemSubheadline:
1230         textStyle = kCTUIFontTextStyleSubhead;
1231         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1232         break;
1233     case CSSValueAppleSystemFootnote:
1234         textStyle = kCTUIFontTextStyleFootnote;
1235         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1236         break;
1237     case CSSValueAppleSystemCaption1:
1238         textStyle = kCTUIFontTextStyleCaption1;
1239         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1240         break;
1241     case CSSValueAppleSystemCaption2:
1242         textStyle = kCTUIFontTextStyleCaption2;
1243         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1244         break;
1245
1246     // Short version.
1247     case CSSValueAppleSystemShortHeadline:
1248         textStyle = kCTUIFontTextStyleShortHeadline;
1249         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1250         break;
1251     case CSSValueAppleSystemShortBody:
1252         textStyle = kCTUIFontTextStyleShortBody;
1253         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1254         break;
1255     case CSSValueAppleSystemShortSubheadline:
1256         textStyle = kCTUIFontTextStyleShortSubhead;
1257         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1258         break;
1259     case CSSValueAppleSystemShortFootnote:
1260         textStyle = kCTUIFontTextStyleShortFootnote;
1261         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1262         break;
1263     case CSSValueAppleSystemShortCaption1:
1264         textStyle = kCTUIFontTextStyleShortCaption1;
1265         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1266         break;
1267
1268     // Tall version.
1269     case CSSValueAppleSystemTallBody:
1270         textStyle = kCTUIFontTextStyleTallBody;
1271         fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(textStyle, contentSizeCategory(), 0));
1272         break;
1273
1274     default:
1275         textStyle = kCTFontDescriptorTextStyleEmphasized;
1276         fontDescriptor = adoptCF(CTFontDescriptorCreateForUIType(kCTFontUIFontSystem, 0, nullptr));
1277     }
1278
1279     ASSERT(fontDescriptor);
1280     RetainPtr<CTFontRef> font = adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1281     font = preparePlatformFont(font.get(), fontDescription.textRenderingMode(), nullptr, nullptr, fontDescription.featureSettings(), fontDescription.variantSettings());
1282     fontDescription.setIsAbsoluteSize(true);
1283     fontDescription.setOneFamily(textStyle);
1284     fontDescription.setSpecifiedSize(CTFontGetSize(font.get()));
1285     fontDescription.setWeight(fontWeightFromCoreText(FontCache::weightOfCTFont(font.get())));
1286     fontDescription.setItalic(FontItalicOff);
1287 }
1288
1289 #if ENABLE(VIDEO)
1290 String RenderThemeIOS::mediaControlsStyleSheet()
1291 {
1292 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1293     if (m_mediaControlsStyleSheet.isEmpty()) {
1294         StringBuilder builder;
1295         builder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"css"] encoding:NSUTF8StringEncoding error:nil]);
1296         m_mediaControlsStyleSheet = builder.toString();
1297     }
1298     return m_mediaControlsStyleSheet;
1299 #else
1300     return emptyString();
1301 #endif
1302 }
1303
1304 String RenderThemeIOS::mediaControlsScript()
1305 {
1306 #if ENABLE(MEDIA_CONTROLS_SCRIPT)
1307     if (m_mediaControlsScript.isEmpty()) {
1308         StringBuilder scriptBuilder;
1309         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsLocalizedStrings" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1310         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsApple" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1311         scriptBuilder.append([NSString stringWithContentsOfFile:[[NSBundle bundleForClass:[WebCoreRenderThemeBundle class]] pathForResource:@"mediaControlsiOS" ofType:@"js"] encoding:NSUTF8StringEncoding error:nil]);
1312         m_mediaControlsScript = scriptBuilder.toString();
1313     }
1314     return m_mediaControlsScript;
1315 #else
1316     return emptyString();
1317 #endif
1318 }
1319 #endif // ENABLE(VIDEO)
1320
1321 Color RenderThemeIOS::systemColor(CSSValueID cssValueID) const
1322 {
1323     auto addResult = m_systemColorCache.add(cssValueID, Color());
1324     if (!addResult.isNewEntry)
1325         return addResult.iterator->value;
1326
1327     Color color;
1328     switch (cssValueID) {
1329     case CSSValueAppleWirelessPlaybackTargetActive:
1330         color = [getUIColorClass() systemBlueColor].CGColor;
1331         break;
1332     case CSSValueAppleSystemBlue:
1333         color = [getUIColorClass() systemBlueColor].CGColor;
1334         break;
1335     case CSSValueAppleSystemGray:
1336         color = [getUIColorClass() systemGrayColor].CGColor;
1337         break;
1338     case CSSValueAppleSystemGreen:
1339         color = [getUIColorClass() systemGreenColor].CGColor;
1340         break;
1341     case CSSValueAppleSystemOrange:
1342         color = [getUIColorClass() systemOrangeColor].CGColor;
1343         break;
1344     case CSSValueAppleSystemPink:
1345         color = [getUIColorClass() systemPinkColor].CGColor;
1346         break;
1347     case CSSValueAppleSystemRed:
1348         color = [getUIColorClass() systemRedColor].CGColor;
1349         break;
1350     case CSSValueAppleSystemYellow:
1351         color = [getUIColorClass() systemYellowColor].CGColor;
1352         break;
1353     default:
1354         break;
1355     }
1356
1357     if (!color.isValid())
1358         color = RenderTheme::systemColor(cssValueID);
1359
1360     addResult.iterator->value = color;
1361
1362     return addResult.iterator->value;
1363 }
1364
1365 #if ENABLE(ATTACHMENT_ELEMENT)
1366
1367 const CGSize attachmentSize = { 160, 119 };
1368
1369 const CGFloat attachmentBorderRadius = 16;
1370 static Color attachmentBorderColor() { return Color(204, 204, 204); }
1371
1372 static Color attachmentProgressColor() { return Color(222, 222, 222); }
1373 const CGFloat attachmentProgressBorderThickness = 3;
1374
1375 const CGFloat attachmentProgressSize = 36;
1376 const CGFloat attachmentIconSize = 48;
1377
1378 const CGFloat attachmentItemMargin = 8;
1379
1380 const CGFloat attachmentWrappingTextMaximumWidth = 140;
1381 const CFIndex attachmentWrappingTextMaximumLineCount = 2;
1382
1383 static RetainPtr<CTFontRef> attachmentActionFont()
1384 {
1385     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortFootnote, RenderThemeIOS::contentSizeCategory(), 0));
1386     RetainPtr<CTFontDescriptorRef> emphasizedFontDescriptor = adoptCF(CTFontDescriptorCreateCopyWithAttributes(fontDescriptor.get(),
1387         (CFDictionaryRef)@{
1388             (id)kCTFontDescriptorTextStyleAttribute: (id)kCTFontDescriptorTextStyleEmphasized
1389     }));
1390     return adoptCF(CTFontCreateWithFontDescriptor(emphasizedFontDescriptor.get(), 0, nullptr));
1391 }
1392
1393 static UIColor *attachmentActionColor(const RenderAttachment& attachment)
1394 {
1395     return [getUIColorClass() colorWithCGColor:cachedCGColor(attachment.style().visitedDependentColor(CSSPropertyColor))];
1396 }
1397
1398 static RetainPtr<CTFontRef> attachmentTitleFont()
1399 {
1400     RetainPtr<CTFontDescriptorRef> fontDescriptor = adoptCF(CTFontDescriptorCreateWithTextStyle(kCTUIFontTextStyleShortCaption1, RenderThemeIOS::contentSizeCategory(), 0));
1401     return adoptCF(CTFontCreateWithFontDescriptor(fontDescriptor.get(), 0, nullptr));
1402 }
1403
1404 static UIColor *attachmentTitleColor() { return [getUIColorClass() systemGrayColor]; }
1405
1406 static RetainPtr<CTFontRef> attachmentSubtitleFont() { return attachmentTitleFont(); }
1407 static UIColor *attachmentSubtitleColor() { return [getUIColorClass() systemGrayColor]; }
1408
1409 struct AttachmentInfo {
1410     explicit AttachmentInfo(const RenderAttachment&);
1411
1412     FloatRect iconRect;
1413     FloatRect attachmentRect;
1414     FloatRect progressRect;
1415
1416     BOOL hasProgress { NO };
1417     float progress;
1418
1419     RetainPtr<UIImage> icon;
1420
1421     int baseline { 0 };
1422
1423     struct LabelLine {
1424         FloatRect rect;
1425         RetainPtr<CTLineRef> line;
1426     };
1427     Vector<LabelLine> lines;
1428
1429     CGFloat contentYOrigin { 0 };
1430
1431 private:
1432     void buildWrappedLines(const String&, CTFontRef, UIColor *, unsigned maximumLineCount);
1433     void buildSingleLine(const String&, CTFontRef, UIColor *);
1434
1435     void addLine(CTLineRef);
1436 };
1437
1438 void AttachmentInfo::addLine(CTLineRef line)
1439 {
1440     CGRect lineBounds = CTLineGetBoundsWithOptions(line, kCTLineBoundsExcludeTypographicLeading);
1441     CGFloat trailingWhitespaceWidth = CTLineGetTrailingWhitespaceWidth(line);
1442     CGFloat lineWidthIgnoringTrailingWhitespace = lineBounds.size.width - trailingWhitespaceWidth;
1443     CGFloat lineHeight = CGCeiling(lineBounds.size.height + lineBounds.origin.y);
1444
1445     CGFloat xOffset = (attachmentRect.width() / 2) - (lineWidthIgnoringTrailingWhitespace / 2);
1446     LabelLine labelLine;
1447     labelLine.line = line;
1448     labelLine.rect = FloatRect(xOffset, 0, lineWidthIgnoringTrailingWhitespace, lineHeight);
1449
1450     lines.append(labelLine);
1451 }
1452
1453 void AttachmentInfo::buildWrappedLines(const String& text, CTFontRef font, UIColor *color, unsigned maximumLineCount)
1454 {
1455     if (text.isEmpty())
1456         return;
1457
1458     NSDictionary *textAttributes = @{
1459         (id)kCTFontAttributeName: (id)font,
1460         (id)kCTForegroundColorAttributeName: color
1461     };
1462     RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1463     RetainPtr<CTFramesetterRef> framesetter = adoptCF(CTFramesetterCreateWithAttributedString((CFAttributedStringRef)attributedText.get()));
1464
1465     CFRange fitRange;
1466     CGSize textSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter.get(), CFRangeMake(0, 0), nullptr, CGSizeMake(attachmentWrappingTextMaximumWidth, CGFLOAT_MAX), &fitRange);
1467
1468     RetainPtr<CGPathRef> textPath = adoptCF(CGPathCreateWithRect(CGRectMake(0, 0, textSize.width, textSize.height), nullptr));
1469     RetainPtr<CTFrameRef> textFrame = adoptCF(CTFramesetterCreateFrame(framesetter.get(), fitRange, textPath.get(), nullptr));
1470
1471     CFArrayRef ctLines = CTFrameGetLines(textFrame.get());
1472     CFIndex lineCount = CFArrayGetCount(ctLines);
1473     if (!lineCount)
1474         return;
1475
1476     // Lay out and record the first (maximumLineCount - 1) lines.
1477     CFIndex lineIndex = 0;
1478     CFIndex nonTruncatedLineCount = std::min<CFIndex>(maximumLineCount - 1, lineCount);
1479     for (; lineIndex < nonTruncatedLineCount; ++lineIndex)
1480         addLine((CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex));
1481
1482     if (lineIndex == lineCount)
1483         return;
1484
1485     // We had text that didn't fit in the first (maximumLineCount - 1) lines.
1486     // Combine it into one last line, and center-truncate it.
1487     CTLineRef firstRemainingLine = (CTLineRef)CFArrayGetValueAtIndex(ctLines, lineIndex);
1488     CFIndex remainingRangeStart = CTLineGetStringRange(firstRemainingLine).location;
1489     NSRange remainingRange = NSMakeRange(remainingRangeStart, [attributedText length] - remainingRangeStart);
1490     NSAttributedString *remainingString = [attributedText attributedSubstringFromRange:remainingRange];
1491     RetainPtr<CTLineRef> remainingLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)remainingString));
1492     RetainPtr<NSAttributedString> ellipsisString = adoptNS([[NSAttributedString alloc] initWithString:@"\u2026" attributes:textAttributes]);
1493     RetainPtr<CTLineRef> ellipsisLine = adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)ellipsisString.get()));
1494     RetainPtr<CTLineRef> truncatedLine = adoptCF(CTLineCreateTruncatedLine(remainingLine.get(), attachmentWrappingTextMaximumWidth, kCTLineTruncationMiddle, ellipsisLine.get()));
1495
1496     if (!truncatedLine)
1497         truncatedLine = remainingLine;
1498
1499     addLine(truncatedLine.get());
1500 }
1501
1502 void AttachmentInfo::buildSingleLine(const String& text, CTFontRef font, UIColor *color)
1503 {
1504     if (text.isEmpty())
1505         return;
1506
1507     NSDictionary *textAttributes = @{
1508         (id)kCTFontAttributeName: (id)font,
1509         (id)kCTForegroundColorAttributeName: color
1510     };
1511     RetainPtr<NSAttributedString> attributedText = adoptNS([[NSAttributedString alloc] initWithString:text attributes:textAttributes]);
1512
1513     addLine(adoptCF(CTLineCreateWithAttributedString((CFAttributedStringRef)attributedText.get())).get());
1514 }
1515
1516 static BOOL getAttachmentProgress(const RenderAttachment& attachment, float& progress)
1517 {
1518     String progressString = attachment.attachmentElement().attributeWithoutSynchronization(progressAttr);
1519     if (progressString.isEmpty())
1520         return NO;
1521     bool validProgress;
1522     progress = std::max<float>(std::min<float>(progressString.toFloat(&validProgress), 1), 0);
1523     return validProgress;
1524 }
1525
1526 static RetainPtr<UIImage> iconForAttachment(const RenderAttachment& attachment, FloatSize& size)
1527 {
1528     auto documentInteractionController = adoptNS([[getUIDocumentInteractionControllerClass() alloc] init]);
1529
1530     String fileName;
1531     if (File* file = attachment.attachmentElement().file())
1532         fileName = file->name();
1533
1534     if (fileName.isEmpty())
1535         fileName = attachment.attachmentElement().attachmentTitle();
1536     [documentInteractionController setName:fileName];
1537
1538     String attachmentType = attachment.attachmentElement().attachmentType();
1539     if (!attachmentType.isEmpty()) {
1540         auto attachmentTypeCF = attachmentType.createCFString();
1541         RetainPtr<CFStringRef> UTI;
1542         if (isDeclaredUTI(attachmentTypeCF.get()))
1543             UTI = attachmentTypeCF;
1544         else
1545             UTI = UTIFromMIMEType(attachmentTypeCF.get());
1546
1547         [documentInteractionController setUTI:static_cast<NSString *>(UTI.get())];
1548     }
1549
1550     NSArray *icons = [documentInteractionController icons];
1551     if (!icons.count)
1552         return nil;
1553
1554     RetainPtr<UIImage> result = icons.lastObject;
1555
1556     BOOL useHeightForClosestMatch = [result size].height > [result size].width;
1557     CGFloat bestMatchRatio = -1;
1558
1559     for (UIImage *icon in icons) {
1560         CGFloat iconSize = useHeightForClosestMatch ? icon.size.height : icon.size.width;
1561
1562         CGFloat matchRatio = (attachmentIconSize / iconSize) - 1.0f;
1563         if (matchRatio < 0.3f) {
1564             matchRatio = CGFAbs(matchRatio);
1565             if ((bestMatchRatio == -1) || (matchRatio < bestMatchRatio)) {
1566                 result = icon;
1567                 bestMatchRatio = matchRatio;
1568             }
1569         }
1570     }
1571
1572     CGFloat iconAspect = [result size].width / [result size].height;
1573     size = largestRectWithAspectRatioInsideRect(iconAspect, FloatRect(0, 0, attachmentIconSize, attachmentIconSize)).size();
1574
1575     return result;
1576 }
1577
1578 AttachmentInfo::AttachmentInfo(const RenderAttachment& attachment)
1579 {
1580     attachmentRect = FloatRect(0, 0, attachmentSize.width, attachmentSize.height);
1581
1582     hasProgress = getAttachmentProgress(attachment, progress);
1583
1584     String title = attachment.attachmentElement().attachmentTitle();
1585     String action = attachment.attachmentElement().attributeWithoutSynchronization(actionAttr);
1586     String subtitle = attachment.attachmentElement().attributeWithoutSynchronization(subtitleAttr);
1587
1588     CGFloat yOffset = 0;
1589
1590     if (hasProgress) {
1591         progressRect = FloatRect((attachmentRect.width() / 2) - (attachmentProgressSize / 2), 0, attachmentProgressSize, attachmentProgressSize);
1592         yOffset += attachmentProgressSize + attachmentItemMargin;
1593     }
1594
1595     if (action.isEmpty() && !hasProgress) {
1596         FloatSize iconSize;
1597         icon = iconForAttachment(attachment, iconSize);
1598         if (icon) {
1599             iconRect = FloatRect(FloatPoint((attachmentRect.width() / 2) - (iconSize.width() / 2), 0), iconSize);
1600             yOffset += iconRect.height() + attachmentItemMargin;
1601         }
1602     } else
1603         buildWrappedLines(action, attachmentActionFont().get(), attachmentActionColor(attachment), attachmentWrappingTextMaximumLineCount);
1604
1605     bool forceSingleLineTitle = !action.isEmpty() || !subtitle.isEmpty() || hasProgress;
1606     buildWrappedLines(title, attachmentTitleFont().get(), attachmentTitleColor(), forceSingleLineTitle ? 1 : attachmentWrappingTextMaximumLineCount);
1607     buildSingleLine(subtitle, attachmentSubtitleFont().get(), attachmentSubtitleColor());
1608
1609     if (!lines.isEmpty()) {
1610         for (auto& line : lines) {
1611             line.rect.setY(yOffset);
1612             yOffset += line.rect.height() + attachmentItemMargin;
1613         }
1614     }
1615
1616     yOffset -= attachmentItemMargin;
1617
1618     contentYOrigin = (attachmentRect.height() / 2) - (yOffset / 2);
1619 }
1620
1621 LayoutSize RenderThemeIOS::attachmentIntrinsicSize(const RenderAttachment&) const
1622 {
1623     return LayoutSize(FloatSize(attachmentSize));
1624 }
1625
1626 int RenderThemeIOS::attachmentBaseline(const RenderAttachment& attachment) const
1627 {
1628     AttachmentInfo info(attachment);
1629     return info.baseline;
1630 }
1631
1632 static void paintAttachmentIcon(GraphicsContext& context, AttachmentInfo& info)
1633 {
1634     if (!info.icon)
1635         return;
1636
1637     RefPtr<Image> iconImage = BitmapImage::create([info.icon CGImage]);
1638     if (!iconImage)
1639         return;
1640
1641     context.drawImage(*iconImage, info.iconRect);
1642 }
1643
1644 static void paintAttachmentText(GraphicsContext& context, AttachmentInfo& info)
1645 {
1646     for (const auto& line : info.lines) {
1647         GraphicsContextStateSaver saver(context);
1648
1649         context.translate(toFloatSize(line.rect.minXMaxYCorner()));
1650         context.scale(FloatSize(1, -1));
1651
1652         CGContextSetTextPosition(context.platformContext(), 0, 0);
1653         CTLineDraw(line.line.get(), context.platformContext());
1654     }
1655 }
1656
1657 static void paintAttachmentProgress(GraphicsContext& context, AttachmentInfo& info)
1658 {
1659     GraphicsContextStateSaver saver(context);
1660
1661     context.setStrokeThickness(attachmentProgressBorderThickness);
1662     context.setStrokeColor(attachmentProgressColor());
1663     context.setFillColor(attachmentProgressColor());
1664     context.strokeEllipse(info.progressRect);
1665
1666     FloatPoint center = info.progressRect.center();
1667
1668     Path progressPath;
1669     progressPath.moveTo(center);
1670     progressPath.addLineTo(FloatPoint(center.x(), info.progressRect.y()));
1671     progressPath.addArc(center, info.progressRect.width() / 2, -M_PI_2, info.progress * 2 * M_PI - M_PI_2, 0);
1672     progressPath.closeSubpath();
1673     context.fillPath(progressPath);
1674 }
1675
1676 static Path attachmentBorderPath(AttachmentInfo& info)
1677 {
1678     Path borderPath;
1679     borderPath.addRoundedRect(info.attachmentRect, FloatSize(attachmentBorderRadius, attachmentBorderRadius));
1680     return borderPath;
1681 }
1682
1683 static void paintAttachmentBorder(GraphicsContext& context, Path& borderPath)
1684 {
1685     context.setStrokeColor(attachmentBorderColor());
1686     context.setStrokeThickness(1);
1687     context.strokePath(borderPath);
1688 }
1689
1690 bool RenderThemeIOS::paintAttachment(const RenderObject& renderer, const PaintInfo& paintInfo, const IntRect& paintRect)
1691 {
1692     if (!is<RenderAttachment>(renderer))
1693         return false;
1694
1695     const RenderAttachment& attachment = downcast<RenderAttachment>(renderer);
1696
1697     AttachmentInfo info(attachment);
1698
1699     GraphicsContext& context = paintInfo.context();
1700     GraphicsContextStateSaver saver(context);
1701
1702     context.translate(toFloatSize(paintRect.location()));
1703
1704     Path borderPath = attachmentBorderPath(info);
1705     paintAttachmentBorder(context, borderPath);
1706     context.clipPath(borderPath);
1707
1708     context.translate(FloatSize(0, info.contentYOrigin));
1709
1710     if (info.hasProgress)
1711         paintAttachmentProgress(context, info);
1712     else if (info.icon)
1713         paintAttachmentIcon(context, info);
1714
1715     paintAttachmentText(context, info);
1716
1717     return true;
1718 }
1719
1720 #endif // ENABLE(ATTACHMENT_ELEMENT)
1721
1722 } // namespace WebCore
1723
1724 #endif //PLATFORM(IOS)