From 6cd205329373efe5a1629518c2875724cc79dce3 Mon Sep 17 00:00:00 2001 From: Michael Drake Date: Thu, 4 Mar 2021 21:22:06 +0000 Subject: Units: Add support for length unit conversion to libcss. Currently only used for unit conversion. --- src/select/Makefile | 2 +- src/select/hash.c | 2 + src/select/mq.h | 103 +---------- src/select/unit.c | 498 ++++++++++++++++++++++++++++++++++++++++++++++++++++ src/select/unit.h | 169 ++++++++++++++++++ 5 files changed, 677 insertions(+), 97 deletions(-) create mode 100644 src/select/unit.c create mode 100644 src/select/unit.h (limited to 'src') diff --git a/src/select/Makefile b/src/select/Makefile index 8b47673..f5ddb18 100644 --- a/src/select/Makefile +++ b/src/select/Makefile @@ -2,6 +2,6 @@ select_generator: python3 src/select/select_generator.py -DIR_SOURCES := arena.c computed.c dispatch.c hash.c select.c font_face.c format_list_style.c +DIR_SOURCES := arena.c computed.c dispatch.c hash.c select.c font_face.c format_list_style.c unit.c include $(NSBUILD)/Makefile.subdir diff --git a/src/select/hash.c b/src/select/hash.c index 4b11702..4dedec9 100644 --- a/src/select/hash.c +++ b/src/select/hash.c @@ -8,6 +8,8 @@ #include #include +#include + #include "stylesheet.h" #include "select/hash.h" #include "select/mq.h" diff --git a/src/select/mq.h b/src/select/mq.h index 6f98387..080a6ba 100644 --- a/src/select/mq.h +++ b/src/select/mq.h @@ -10,96 +10,7 @@ #define css_select_mq_h_ #include "select/helpers.h" - -static inline css_fixed css_len2px( - css_fixed length, - css_unit unit, - const css_media *media) -{ - css_fixed px_per_unit; - - switch (unit) { - case CSS_UNIT_VI: - /* TODO: Assumes writing mode. */ - unit = CSS_UNIT_VW; - break; - case CSS_UNIT_VB: - /* TODO: Assumes writing mode. */ - unit = CSS_UNIT_VH; - break; - case CSS_UNIT_VMIN: - unit = (media->height < media->width) ? - CSS_UNIT_VH : CSS_UNIT_VW; - break; - case CSS_UNIT_VMAX: - unit = (media->width > media->height) ? - CSS_UNIT_VH : CSS_UNIT_VW; - break; - default: - break; - } - - switch (unit) { - case CSS_UNIT_EM: - case CSS_UNIT_EX: - case CSS_UNIT_CH: - { - px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72); - - /* TODO: Handling these as fixed ratios of CSS_UNIT_EM. */ - switch (unit) { - case CSS_UNIT_EX: - px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.6)); - break; - case CSS_UNIT_CH: - px_per_unit = FMUL(px_per_unit, FLTTOFIX(0.4)); - break; - default: - break; - } - } - break; - case CSS_UNIT_PX: - return length; - case CSS_UNIT_IN: - px_per_unit = F_96; - break; - case CSS_UNIT_CM: - px_per_unit = FDIV(F_96, FLTTOFIX(2.54)); - break; - case CSS_UNIT_MM: - px_per_unit = FDIV(F_96, FLTTOFIX(25.4)); - break; - case CSS_UNIT_Q: - px_per_unit = FDIV(F_96, FLTTOFIX(101.6)); - break; - case CSS_UNIT_PT: - px_per_unit = FDIV(F_96, F_72); - break; - case CSS_UNIT_PC: - px_per_unit = FDIV(F_96, INTTOFIX(6)); - break; - case CSS_UNIT_REM: - px_per_unit = FDIV(FMUL(media->client_font_size, F_96), F_72); - break; - case CSS_UNIT_VH: - px_per_unit = FDIV(media->height, F_100); - break; - case CSS_UNIT_VW: - px_per_unit = FDIV(media->width, F_100); - break; - default: - px_per_unit = 0; - break; - } - - /* Ensure we round px_per_unit to the nearest whole number of pixels: - * the use of FIXTOINT() below will truncate. */ - px_per_unit += F_0_5; - - /* Calculate total number of pixels */ - return FMUL(length, TRUNCATEFIX(px_per_unit)); -} +#include "select/unit.h" static inline bool mq_match_feature_range_length_op1( css_mq_feature_op op, @@ -114,9 +25,9 @@ static inline bool mq_match_feature_range_length_op1( } if (value->data.dim.unit != UNIT_PX) { - v = css_len2px(value->data.dim.len, - css__to_css_unit(value->data.dim.unit), - media); + v = css_unit_len2px_mq(media, + value->data.dim.len, + css__to_css_unit(value->data.dim.unit)); } else { v = value->data.dim.len; } @@ -149,9 +60,9 @@ static inline bool mq_match_feature_range_length_op2( } if (value->data.dim.unit != UNIT_PX) { - v = css_len2px(value->data.dim.len, - css__to_css_unit(value->data.dim.unit), - media); + v = css_unit_len2px_mq(media, + value->data.dim.len, + css__to_css_unit(value->data.dim.unit)); } else { v = value->data.dim.len; } diff --git a/src/select/unit.c b/src/select/unit.c new file mode 100644 index 0000000..f9ecf83 --- /dev/null +++ b/src/select/unit.c @@ -0,0 +1,498 @@ +/* + * This file is part of LibCSS + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2021 Michael Drake + */ + +#include + +#include "utils/utils.h" + +#include "propget.h" +#include "unit.h" + +/** + * Map viewport-relative length units to either vh or vw. + * + * Non-viewport-relative units are unchanged. + * + * \param[in] style Reference style. + * \param[in] viewport_height Viewport height in px. + * \param[in] viewport_width Viewport width in px. + * \param[in] unit Unit to map. + * \return the mapped unit. + */ +static inline css_unit css_unit__map_viewport_units( + const css_computed_style *style, + const css_fixed viewport_height, + const css_fixed viewport_width, + const css_unit unit) +{ + switch (unit) { + case CSS_UNIT_VI: + return (style != NULL && get_writing_mode(style) != + CSS_WRITING_MODE_HORIZONTAL_TB) ? + CSS_UNIT_VH : CSS_UNIT_VW; + + case CSS_UNIT_VB: + return (style != NULL && get_writing_mode(style) != + CSS_WRITING_MODE_HORIZONTAL_TB) ? + CSS_UNIT_VW : CSS_UNIT_VH; + + case CSS_UNIT_VMIN: + return (viewport_height < viewport_width) ? + CSS_UNIT_VH : CSS_UNIT_VW; + + case CSS_UNIT_VMAX: + return (viewport_height > viewport_width) ? + CSS_UNIT_VH : CSS_UNIT_VW; + + default: + return unit; + } +} + +/** + * Convert an absolute length to points (pt). + * + * \param[in] style Style to get font-size from, or NULL. + * \param[in] viewport_height Client viewport height. + * \param[in] viewport_width Client viewport width. + * \param[in] length Length to convert. + * \param[in] unit Current unit of length. + * \return length in points (pt). + */ +static inline css_fixed css_unit__absolute_len2pt( + const css_computed_style *style, + const css_fixed viewport_height, + const css_fixed viewport_width, + const css_fixed length, + const css_unit unit) +{ + /* Length must not be relative */ + assert(unit != CSS_UNIT_EM && + unit != CSS_UNIT_EX && + unit != CSS_UNIT_CH && + unit != CSS_UNIT_REM); + + switch (css_unit__map_viewport_units(style, + viewport_height, + viewport_width, + unit)) { + case CSS_UNIT_PX: + return FDIV(FMUL(length, F_72), F_96); + + case CSS_UNIT_IN: + return FMUL(length, F_72); + + case CSS_UNIT_CM: + return FMUL(length, FDIV(F_72, FLTTOFIX(2.54))); + + case CSS_UNIT_MM: + return FMUL(length, FDIV(F_72, FLTTOFIX(25.4))); + + case CSS_UNIT_Q: + return FMUL(length, FDIV(F_72, FLTTOFIX(101.6))); + + case CSS_UNIT_PT: + return length; + + case CSS_UNIT_PC: + return FMUL(length, INTTOFIX(12)); + + case CSS_UNIT_VH: + return FDIV(FMUL(FDIV(FMUL(length, viewport_height), F_100), + F_72), F_96); + + case CSS_UNIT_VW: + return FDIV(FMUL(FDIV(FMUL(length, viewport_width), F_100), + F_72), F_96); + + default: + return 0; + } +} + +/* Exported function, documented in unit.h. */ +css_fixed css_unit_font_size_len2pt( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit) +{ + return css_unit__absolute_len2pt( + ctx->ref_style, + ctx->viewport_height, + ctx->viewport_width, + length, + unit); +} + +/** + * Get font size from a style in CSS pixels. + * + * The style should have font size in absolute units. + * + * \param[in] style Style to get font-size from, or NULL. + * \param[in] font_size_default Client font size for NULL style. + * \param[in] font_size_minimum Client minimum font size clamp. + * \param[in] viewport_height Client viewport height. + * \param[in] viewport_width Client viewport width. + * \return font-size in CSS pixels. + */ +static inline css_fixed css_unit__font_size_px( + const css_computed_style *style, + const css_fixed font_size_default, + const css_fixed font_size_minimum, + const css_fixed viewport_height, + const css_fixed viewport_width) +{ + css_fixed font_length = 0; + css_unit font_unit = CSS_UNIT_PT; + + if (style == NULL) { + return font_size_default; + } + + get_font_size(style, &font_length, &font_unit); + + if (font_unit != CSS_UNIT_PX) { + font_length = css_unit__absolute_len2pt(style, + viewport_height, + viewport_width, + font_length, + font_unit); + + /* Convert from pt to CSS pixels.*/ + font_length = FDIV(FMUL(font_length, F_96), F_72); + } + + /* Clamp to configured minimum */ + if (font_length < font_size_minimum) { + font_length = font_size_minimum; + } + + return font_length; +} + +/** + * Get the number of CSS pixels for a given unit. + * + * \param[in] measure Client callback for font measuring. + * \param[in] ref_style Reference style. (Element or parent, or NULL). + * \param[in] root_style Root element style or NULL. + * \param[in] font_size_default Client default font size in CSS pixels. + * \param[in] font_size_minimum Client minimum font size in CSS pixels. + * \param[in] viewport_height Viewport height in CSS pixels. + * \param[in] viewport_width Viewport width in CSS pixels. + * \param[in] unit The unit to convert from. + * \param[in] pw Client private word for measure callback. + * \return Number of CSS pixels equivalent to the given unit. + */ +static inline css_fixed css_unit__px_per_unit( + const css_unit_len_measure measure, + const css_computed_style *ref_style, + const css_computed_style *root_style, + const css_fixed font_size_default, + const css_fixed font_size_minimum, + const css_fixed viewport_height, + const css_fixed viewport_width, + const css_unit unit, + void *pw) +{ + switch (css_unit__map_viewport_units( + ref_style, + viewport_height, + viewport_width, + unit)) { + case CSS_UNIT_EM: + return css_unit__font_size_px( + ref_style, + font_size_default, + font_size_minimum, + viewport_height, + viewport_width); + + case CSS_UNIT_EX: + if (measure != NULL) { + return measure(pw, ref_style, CSS_UNIT_EX); + } + return FMUL(css_unit__font_size_px( + ref_style, + font_size_default, + font_size_minimum, + viewport_height, + viewport_width), FLTTOFIX(0.6)); + + case CSS_UNIT_CH: + if (measure != NULL) { + return measure(pw, ref_style, CSS_UNIT_CH); + } + return FMUL(css_unit__font_size_px( + ref_style, + font_size_default, + font_size_minimum, + viewport_height, + viewport_width), FLTTOFIX(0.4)); + + case CSS_UNIT_PX: + return F_1; + + case CSS_UNIT_IN: + return F_96; + + case CSS_UNIT_CM: + return FDIV(F_96, FLTTOFIX(2.54)); + + case CSS_UNIT_MM: + return FDIV(F_96, FLTTOFIX(25.4)); + + case CSS_UNIT_Q: + return FDIV(F_96, FLTTOFIX(101.6)); + + case CSS_UNIT_PT: + return FDIV(F_96, F_72); + + case CSS_UNIT_PC: + return FDIV(F_96, INTTOFIX(6)); + + case CSS_UNIT_REM: + return css_unit__font_size_px( + root_style, + font_size_default, + font_size_minimum, + viewport_height, + viewport_width); + + case CSS_UNIT_VH: + return FDIV(viewport_width, F_100); + + case CSS_UNIT_VW: + return FDIV(viewport_height, F_100); + + default: + return 0; + } +} + +/* Exported function, documented in unit.h. */ +css_fixed css_unit_len2px_mq( + const css_media *media, + const css_fixed length, + const css_unit unit) +{ + /* In the media query context there is no reference or root element + * style, so these are hardcoded to NULL. */ + css_fixed px_per_unit = css_unit__px_per_unit( + NULL, + NULL, + NULL, + media->client_font_size, + INTTOFIX(0), + media->height, + media->width, + unit, + NULL); + + /* Ensure we round px_per_unit to the nearest whole number of pixels: + * the use of FIXTOINT() below will truncate. */ + px_per_unit += F_0_5; + + /* Calculate total number of pixels */ + return FMUL(length, TRUNCATEFIX(px_per_unit)); +} + +/* Exported function, documented in unit.h. */ +css_fixed css_unit_len2css_px( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit) +{ + css_fixed px_per_unit = css_unit__px_per_unit( + ctx->measure, + ctx->ref_style, + ctx->root_style, + ctx->font_size_default, + ctx->font_size_minimum, + ctx->viewport_height, + ctx->viewport_width, + unit, + ctx->pw); + + /* Ensure we round px_per_unit to the nearest whole number of pixels: + * the use of FIXTOINT() below will truncate. */ + px_per_unit += F_0_5; + + /* Calculate total number of pixels */ + return FMUL(length, TRUNCATEFIX(px_per_unit)); +} + +/* Exported function, documented in unit.h. */ +css_fixed css_unit_len2device_px( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit) +{ + css_fixed px_per_unit = css_unit__px_per_unit( + ctx->measure, + ctx->ref_style, + ctx->root_style, + ctx->font_size_default, + ctx->font_size_minimum, + ctx->viewport_height, + ctx->viewport_width, + unit, + ctx->pw); + + px_per_unit = css_unit_css2device_px(px_per_unit, ctx->device_dpi); + + /* Ensure we round px_per_unit to the nearest whole number of pixels: + * the use of FIXTOINT() below will truncate. */ + px_per_unit += F_0_5; + + /* Calculate total number of pixels */ + return FMUL(length, TRUNCATEFIX(px_per_unit)); +} + +/** + * Get font size from a computed style. + * + * The computed style will have font-size with an absolute unit. + * If no computed style is given, the client default font-size will be returned. + * + * \param[in] ctx Length unit conversion context. + * \param[in] style The style to get the font-size for, or NULL. + * \return The font size in absolute units. + */ +static inline css_hint_length css_unit__get_font_size( + const css_unit_len_ctx *ctx, + const css_computed_style *style) +{ + css_hint_length size; + + if (style == NULL) { + size.value = ctx->font_size_default; + size.unit = CSS_UNIT_PX; + } else { + enum css_font_size_e status = get_font_size( + style, &size.value, &size.unit); + + UNUSED(status); + + assert(status == CSS_FONT_SIZE_DIMENSION); + assert(size.unit != CSS_UNIT_EM); + assert(size.unit != CSS_UNIT_EX); + assert(size.unit != CSS_UNIT_PCT); + } + + return size; +} + +/* Exported function, documented in unit.h. */ +css_error css_unit_compute_absolute_font_size( + const css_unit_len_ctx *ctx, + css_hint *size) +{ + css_hint_length ref_len; + + assert(size->status != CSS_FONT_SIZE_INHERIT); + + switch (size->status) { + case CSS_FONT_SIZE_XX_SMALL: /* Fall-through. */ + case CSS_FONT_SIZE_X_SMALL: /* Fall-through. */ + case CSS_FONT_SIZE_SMALL: /* Fall-through. */ + case CSS_FONT_SIZE_MEDIUM: /* Fall-through. */ + case CSS_FONT_SIZE_LARGE: /* Fall-through. */ + case CSS_FONT_SIZE_X_LARGE: /* Fall-through. */ + case CSS_FONT_SIZE_XX_LARGE: + { + static const css_fixed factors[CSS_FONT_SIZE_XX_LARGE] = { + [CSS_FONT_SIZE_XX_SMALL - 1] = FLTTOFIX(0.5625), + [CSS_FONT_SIZE_X_SMALL - 1] = FLTTOFIX(0.6250), + [CSS_FONT_SIZE_SMALL - 1] = FLTTOFIX(0.8125), + [CSS_FONT_SIZE_MEDIUM - 1] = FLTTOFIX(1.0000), + [CSS_FONT_SIZE_LARGE - 1] = FLTTOFIX(1.1250), + [CSS_FONT_SIZE_X_LARGE - 1] = FLTTOFIX(1.5000), + [CSS_FONT_SIZE_XX_LARGE - 1] = FLTTOFIX(2.0000), + }; + assert(CSS_FONT_SIZE_INHERIT == 0); + assert(CSS_FONT_SIZE_XX_SMALL == 1); + + size->data.length.value = FMUL(factors[size->status - 1], + ctx->font_size_default); + size->data.length.unit = CSS_UNIT_PX; + size->status = CSS_FONT_SIZE_DIMENSION; + break; + } + case CSS_FONT_SIZE_LARGER: + ref_len = css_unit__get_font_size(ctx, ctx->ref_style); + size->data.length.value = FMUL(ref_len.value, FLTTOFIX(1.2)); + size->data.length.unit = ref_len.unit; + size->status = CSS_FONT_SIZE_DIMENSION; + break; + + case CSS_FONT_SIZE_SMALLER: + ref_len = css_unit__get_font_size(ctx, ctx->ref_style); + size->data.length.value = FDIV(ref_len.value, FLTTOFIX(1.2)); + size->data.length.unit = ref_len.unit; + size->status = CSS_FONT_SIZE_DIMENSION; + break; + + case CSS_FONT_SIZE_DIMENSION: + /* Convert any relative units to absolute. */ + switch (size->data.length.unit) { + case CSS_UNIT_PCT: + ref_len = css_unit__get_font_size(ctx, ctx->ref_style); + size->data.length.value = FDIV( + FMUL(size->data.length.value, + ref_len.value), INTTOFIX(100)); + size->data.length.unit = ref_len.unit; + break; + + case CSS_UNIT_EM: /* Fall-through */ + case CSS_UNIT_EX: /* Fall-through */ + case CSS_UNIT_CH: + /* Parent relative units. */ + ref_len = css_unit__get_font_size(ctx, ctx->ref_style); + + size->data.length.unit = ref_len.unit; + size->data.length.value = FMUL(size->data.length.value, + ref_len.value); + + switch (size->data.length.unit) { + case CSS_UNIT_EX: + size->data.length.value = FMUL( + size->data.length.value, + FLTTOFIX(0.6)); + break; + + case CSS_UNIT_CH: + size->data.length.value = FMUL( + size->data.length.value, + FLTTOFIX(0.4)); + break; + + default: + break; + } + break; + + case CSS_UNIT_REM: + /* Root element relative units. */ + ref_len = css_unit__get_font_size(ctx, ctx->root_style); + + size->data.length.unit = ref_len.unit; + size->data.length.value = FMUL(size->data.length.value, + ref_len.value); + break; + + default: + break; + } + default: + break; + } + + return CSS_OK; +} diff --git a/src/select/unit.h b/src/select/unit.h new file mode 100644 index 0000000..738c181 --- /dev/null +++ b/src/select/unit.h @@ -0,0 +1,169 @@ +/* + * This file is part of LibCSS + * Licensed under the MIT License, + * http://www.opensource.org/licenses/mit-license.php + * + * Copyright 2021 Michael Drake + */ + +#ifndef css_select_unit_h_ +#define css_select_unit_h_ + +/** + * Client callback for font measuring. + * + * \param[in] pw Client data. + * \param[in] style Style to measure font for, or NULL. + * \param[in] unit Either CSS_UNIT_EX, or CSS_UNIT_CH. + * \return length in CSS pixels. + */ +typedef css_fixed (*css_unit_len_measure)( + void *pw, + const css_computed_style *style, + const css_unit unit); + +/** + * LibCSS unit conversion context. + * + * The client callback is optional. It is used for measuring "ch" + * (glyph '0' advance) and "ex" (height of the letter 'x') units. + * If a NULL pointer is given, LibCSS will use a fixed scaling of + * the "em" unit. + */ +typedef struct css_unit_len_ctx { + /** + * Viewport width in CSS pixels. + * Used if unit is vh, vw, vi, vb, vmin, or vmax. + */ + css_fixed viewport_width; + /** + * Viewport height in CSS pixels. + * Used if unit is vh, vw, vi, vb, vmin, or vmax. + */ + css_fixed viewport_height; + /** + * Client default font size in CSS pixels. + */ + css_fixed font_size_default; + /** + * Client minimum font size in CSS pixels. May be zero. + */ + css_fixed font_size_minimum; + /** + * DPI of the device the style is selected for. + */ + css_fixed device_dpi; + /** + * Reference style. Most of the time, this is the element's style. + * When converting a length for a typographical property, such as + * font-size, then this should be the parent node. If the node has + * no parent then this may be NULL. + */ + const css_computed_style *ref_style; + /** + * Computed style for the document root element. + * May be NULL if unit is not rem. + */ + const css_computed_style *root_style; + /** + * Client private word for measure callback. + */ + void *pw; + /** + * Client callback for font measuring. + */ + const css_unit_len_measure measure; +} css_unit_len_ctx; + +/** + * Convert css pixels to physical pixels. + * + * \param[in] css_pixels Length in css pixels. + * \param[in] device_dpi Device dots per inch. + * \return Length in device pixels. + */ +static inline css_fixed css_unit_css2device_px( + const css_fixed css_pixels, + const css_fixed device_dpi) +{ + return FDIV(FMUL(css_pixels, device_dpi), F_96); +} + +/** + * Convert device pixels to css pixels. + * + * \param[in] device_pixels Length in physical pixels. + * \param[in] device_dpi Device dots per inch. + * \return Length in css pixels. + */ +static inline css_fixed css_unit_device2css_px( + const css_fixed device_pixels, + const css_fixed device_dpi) +{ + return FDIV(FMUL(device_pixels, F_96), device_dpi); +} + +/** + * Convert a length to points (pt). + * + * \param[in] ctx Length unit conversion context. + * \param[in] length Length to convert. + * \param[in] unit Current unit of length. + * \return A length in points. + */ +css_fixed css_unit_font_size_len2pt( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit); + +/** + * Convert a length to CSS pixels. + * + * \param[in] ctx Length unit conversion context. + * \param[in] length Length to convert. + * \param[in] unit Current unit of length. + * \return A length in CSS pixels. + */ +css_fixed css_unit_len2css_px( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit); + +/** + * Convert a length to device pixels. + * + * \param[in] ctx Length unit conversion context. + * \param[in] length Length to convert. + * \param[in] unit Current unit of length. + * \return A length in device pixels. + */ +css_fixed css_unit_len2device_px( + const css_unit_len_ctx *ctx, + const css_fixed length, + const css_unit unit); + +/** + * Convert a length to CSS pixels for a media query context. + * + * \param[in] media Client media specification. + * \param[in] length Length to convert. + * \param[in] unit Current unit of length. + * \return A length in CSS pixels. + */ +css_fixed css_unit_len2px_mq( + const css_media *media, + const css_fixed length, + const css_unit unit); + +/** + * Convert relative font size units to absolute units. + * + * \param[in] ctx Length unit conversion context. + * \param[in,out] size The length to convert. + * \return CSS_OK on success, or appropriate error otherwise. + */ +css_error css_unit_compute_absolute_font_size( + const css_unit_len_ctx *ctx, + css_hint *size); + +#endif -- cgit v1.2.3