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