2 * Copyright (C) 1999 Lars Knoll (knoll@kde.org)
3 * (C) 1999 Antti Koivisto (koivisto@kde.org)
4 * (C) 2001 Dirk Mueller (mueller@kde.org)
5 * (C) 2006 Alexey Proskuryakov (ap@webkit.org)
6 * Copyright (C) 2004, 2005, 2006, 2007, 2008, 2011 Apple Inc. All rights reserved.
7 * Copyright (C) 2008 Torch Mobile Inc. All rights reserved. (http://www.torchmobile.com/)
8 * Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies)
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Library General Public
12 * License as published by the Free Software Foundation; either
13 * version 2 of the License, or (at your option) any later version.
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Library General Public License for more details.
20 * You should have received a copy of the GNU Library General Public License
21 * along with this library; see the file COPYING.LIB. If not, write to
22 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
23 * Boston, MA 02110-1301, USA.
28 #include "ViewportArguments.h"
32 #include "DOMWindow.h"
37 #include "PlatformString.h"
38 #include "ScriptableDocumentParser.h"
44 ViewportAttributes computeViewportAttributes(ViewportArguments args, int desktopWidth, int deviceWidth, int deviceHeight, int deviceDPI, IntSize visibleViewport)
46 ViewportAttributes result;
48 float availableWidth = visibleViewport.width();
49 float availableHeight = visibleViewport.height();
51 ASSERT(availableWidth > 0 && availableHeight > 0);
53 switch (int(args.targetDensityDpi)) {
54 case ViewportArguments::ValueDeviceDPI:
55 args.targetDensityDpi = deviceDPI;
57 case ViewportArguments::ValueLowDPI:
58 args.targetDensityDpi = 120;
60 case ViewportArguments::ValueAuto:
61 case ViewportArguments::ValueMediumDPI:
62 args.targetDensityDpi = 160;
64 case ViewportArguments::ValueHighDPI:
65 args.targetDensityDpi = 240;
69 result.devicePixelRatio = float(deviceDPI / args.targetDensityDpi);
71 // Resolve non-'auto' width and height to pixel values.
72 if (result.devicePixelRatio != 1.0) {
73 availableWidth /= result.devicePixelRatio;
74 availableHeight /= result.devicePixelRatio;
75 deviceWidth /= result.devicePixelRatio;
76 deviceHeight /= result.devicePixelRatio;
79 switch (int(args.width)) {
80 case ViewportArguments::ValueDesktopWidth:
81 args.width = desktopWidth;
83 case ViewportArguments::ValueDeviceWidth:
84 args.width = deviceWidth;
86 case ViewportArguments::ValueDeviceHeight:
87 args.width = deviceHeight;
91 switch (int(args.height)) {
92 case ViewportArguments::ValueDesktopWidth:
93 args.height = desktopWidth;
95 case ViewportArguments::ValueDeviceWidth:
96 args.height = deviceWidth;
98 case ViewportArguments::ValueDeviceHeight:
99 args.height = deviceHeight;
103 // Clamp values to range defined by spec and resolve minimum-scale and maximum-scale values
104 if (args.width != ViewportArguments::ValueAuto)
105 args.width = min(float(10000), max(args.width, float(1)));
106 if (args.height != ViewportArguments::ValueAuto)
107 args.height = min(float(10000), max(args.height, float(1)));
109 if (args.initialScale != ViewportArguments::ValueAuto)
110 args.initialScale = min(float(10), max(args.initialScale, float(0.1)));
111 if (args.minimumScale != ViewportArguments::ValueAuto)
112 args.minimumScale = min(float(10), max(args.minimumScale, float(0.1)));
113 if (args.maximumScale != ViewportArguments::ValueAuto)
114 args.maximumScale = min(float(10), max(args.maximumScale, float(0.1)));
116 // Resolve minimum-scale and maximum-scale values according to spec.
117 if (args.minimumScale == ViewportArguments::ValueAuto)
118 result.minimumScale = float(0.25);
120 result.minimumScale = args.minimumScale;
122 if (args.maximumScale == ViewportArguments::ValueAuto) {
123 result.maximumScale = float(5.0);
124 result.minimumScale = min(float(5.0), result.minimumScale);
126 result.maximumScale = args.maximumScale;
127 result.maximumScale = max(result.minimumScale, result.maximumScale);
129 // Resolve initial-scale value.
130 result.initialScale = args.initialScale;
131 if (result.initialScale == ViewportArguments::ValueAuto) {
132 result.initialScale = availableWidth / desktopWidth;
133 if (args.width != ViewportArguments::ValueAuto)
134 result.initialScale = availableWidth / args.width;
135 if (args.height != ViewportArguments::ValueAuto) {
136 // if 'auto', the initial-scale will be negative here and thus ignored.
137 result.initialScale = max(result.initialScale, availableHeight / args.height);
141 // Constrain initial-scale value to minimum-scale/maximum-scale range.
142 result.initialScale = min(result.maximumScale, max(result.minimumScale, result.initialScale));
144 // Resolve width value.
146 if (args.width != ViewportArguments::ValueAuto)
149 if (args.initialScale == ViewportArguments::ValueAuto)
150 width = desktopWidth;
151 else if (args.height != ViewportArguments::ValueAuto)
152 width = args.height * (availableWidth / availableHeight);
154 width = availableWidth / result.initialScale;
157 // Resolve height value.
159 if (args.height != ViewportArguments::ValueAuto)
160 height = args.height;
162 height = width * availableHeight / availableWidth;
164 // Extend width and height to fill the visual viewport for the resolved initial-scale.
165 width = max(width, availableWidth / result.initialScale);
166 height = max(height, availableHeight / result.initialScale);
167 result.layoutSize.setWidth(static_cast<int>(roundf(width)));
168 result.layoutSize.setHeight(static_cast<int>(roundf(height)));
170 // Update minimum scale factor, to never allow zooming out more than viewport
171 result.minimumScale = max(result.minimumScale, max(availableWidth / width, availableHeight / height));
173 result.userScalable = args.userScalable;
174 // Make maximum and minimum scale equal to the initial scale if user is not allowed to zoom in/out.
175 if (!args.userScalable)
176 result.maximumScale = result.minimumScale = result.initialScale;
181 static float numericPrefix(const String& keyString, const String& valueString, Document* document, bool* ok)
183 // If a prefix of property-value can be converted to a number using strtod,
184 // the value will be that number. The remainder of the string is ignored.
185 // So when String::toFloat says there is an error, it may be a false positive,
186 // and we should check if the valueString prefix was a number.
189 float value = valueString.toFloat(ok, &didReadNumber);
191 if (!didReadNumber) {
193 reportViewportWarning(document, UnrecognizedViewportArgumentValueError, valueString, keyString);
197 reportViewportWarning(document, TruncatedViewportArgumentValueError, valueString, keyString);
202 static float findSizeValue(const String& keyString, const String& valueString, Document* document)
204 // 1) Non-negative number values are translated to px lengths.
205 // 2) Negative number values are translated to auto.
206 // 3) device-width and device-height are used as keywords.
207 // 4) Other keywords and unknown values translate to 0.0.
209 if (equalIgnoringCase(valueString, "desktop-width"))
210 return ViewportArguments::ValueDesktopWidth;
211 if (equalIgnoringCase(valueString, "device-width"))
212 return ViewportArguments::ValueDeviceWidth;
213 if (equalIgnoringCase(valueString, "device-height"))
214 return ViewportArguments::ValueDeviceHeight;
217 float value = numericPrefix(keyString, valueString, document, &ok);
222 return ViewportArguments::ValueAuto;
227 static float findScaleValue(const String& keyString, const String& valueString, Document* document)
229 // 1) Non-negative number values are translated to <number> values.
230 // 2) Negative number values are translated to auto.
231 // 3) yes is translated to 1.0.
232 // 4) device-width and device-height are translated to 10.0.
233 // 5) no and unknown values are translated to 0.0
235 if (equalIgnoringCase(valueString, "yes"))
237 if (equalIgnoringCase(valueString, "no"))
239 if (equalIgnoringCase(valueString, "desktop-width"))
241 if (equalIgnoringCase(valueString, "device-width"))
243 if (equalIgnoringCase(valueString, "device-height"))
247 float value = numericPrefix(keyString, valueString, document, &ok);
252 return ViewportArguments::ValueAuto;
255 reportViewportWarning(document, MaximumScaleTooLargeError, String(), String());
260 static float findUserScalableValue(const String& keyString, const String& valueString, Document* document)
262 // yes and no are used as keywords.
263 // Numbers >= 1, numbers <= -1, device-width and device-height are mapped to yes.
264 // Numbers in the range <-1, 1>, and unknown values, are mapped to no.
266 if (equalIgnoringCase(valueString, "yes"))
268 if (equalIgnoringCase(valueString, "no"))
270 if (equalIgnoringCase(valueString, "desktop-width"))
272 if (equalIgnoringCase(valueString, "device-width"))
274 if (equalIgnoringCase(valueString, "device-height"))
278 float value = numericPrefix(keyString, valueString, document, &ok);
288 static float findTargetDensityDPIValue(const String& keyString, const String& valueString, Document* document)
290 if (equalIgnoringCase(valueString, "device-dpi"))
291 return ViewportArguments::ValueDeviceDPI;
292 if (equalIgnoringCase(valueString, "low-dpi"))
293 return ViewportArguments::ValueLowDPI;
294 if (equalIgnoringCase(valueString, "medium-dpi"))
295 return ViewportArguments::ValueMediumDPI;
296 if (equalIgnoringCase(valueString, "high-dpi"))
297 return ViewportArguments::ValueHighDPI;
300 float value = numericPrefix(keyString, valueString, document, &ok);
302 return ViewportArguments::ValueAuto;
304 if (value < 70 || value > 400) {
305 reportViewportWarning(document, TargetDensityDpiTooSmallOrLargeError, String(), String());
306 return ViewportArguments::ValueAuto;
312 void setViewportFeature(const String& keyString, const String& valueString, Document* document, void* data)
314 ViewportArguments* arguments = static_cast<ViewportArguments*>(data);
316 if (keyString == "width")
317 arguments->width = findSizeValue(keyString, valueString, document);
318 else if (keyString == "height")
319 arguments->height = findSizeValue(keyString, valueString, document);
320 else if (keyString == "initial-scale")
321 arguments->initialScale = findScaleValue(keyString, valueString, document);
322 else if (keyString == "minimum-scale")
323 arguments->minimumScale = findScaleValue(keyString, valueString, document);
324 else if (keyString == "maximum-scale")
325 arguments->maximumScale = findScaleValue(keyString, valueString, document);
326 else if (keyString == "user-scalable")
327 arguments->userScalable = findUserScalableValue(keyString, valueString, document);
328 else if (keyString == "target-densitydpi")
329 arguments->targetDensityDpi = findTargetDensityDPIValue(keyString, valueString, document);
331 reportViewportWarning(document, UnrecognizedViewportArgumentKeyError, keyString, String());
334 static const char* viewportErrorMessageTemplate(ViewportErrorCode errorCode)
336 static const char* const errors[] = {
337 "Viewport argument key \"%replacement1\" not recognized and ignored.",
338 "Viewport argument value \"%replacement1\" for key \"%replacement2\" not recognized. Content ignored.",
339 "Viewport argument value \"%replacement1\" for key \"%replacement2\" was truncated to its numeric prefix.",
340 "Viewport maximum-scale cannot be larger than 10.0. The maximum-scale will be set to 10.0.",
341 "Viewport target-densitydpi has to take a number between 70 and 400 as a valid target dpi, try using \"device-dpi\", \"low-dpi\", \"medium-dpi\" or \"high-dpi\" instead for future compatibility."
344 return errors[errorCode];
347 static MessageLevel viewportErrorMessageLevel(ViewportErrorCode errorCode)
350 case TruncatedViewportArgumentValueError:
351 case TargetDensityDpiTooSmallOrLargeError:
352 return TipMessageLevel;
353 case UnrecognizedViewportArgumentKeyError:
354 case UnrecognizedViewportArgumentValueError:
355 case MaximumScaleTooLargeError:
356 return ErrorMessageLevel;
359 ASSERT_NOT_REACHED();
360 return ErrorMessageLevel;
363 // FIXME: Why is this different from SVGDocumentExtensions parserLineNumber?
364 // FIXME: Callers should probably use ScriptController::eventHandlerLineNumber()
365 static int parserLineNumber(Document* document)
369 ScriptableDocumentParser* parser = document->scriptableDocumentParser();
372 return parser->lineNumber() + 1;
375 void reportViewportWarning(Document* document, ViewportErrorCode errorCode, const String& replacement1, const String& replacement2)
377 Frame* frame = document->frame();
381 String message = viewportErrorMessageTemplate(errorCode);
382 if (!replacement1.isNull())
383 message.replace("%replacement1", replacement1);
384 if (!replacement2.isNull())
385 message.replace("%replacement2", replacement2);
387 frame->domWindow()->console()->addMessage(HTMLMessageSource, LogMessageType, viewportErrorMessageLevel(errorCode), message, parserLineNumber(document), document->url().string());
390 } // namespace WebCore