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