├── NOTICE
├── .lgtm.yml
├── htmlcss-512.png
├── CHANGES.md
├── htmlcss.xcodeproj
└── project.xcworkspace
│ ├── contents.xcworkspacedata
│ └── xcshareddata
│ └── IDEWorkspaceChecks.plist
├── TODO.md
├── htmlcss.pc.in
├── DOCUMENTATION.md
├── htmlcss.h
├── CODE_OF_CONDUCT.md
├── run.h
├── file-private.h
├── image.h
├── file.h
├── dict.h
├── sha3.h
├── pool.h
├── TESTSUITE.md
├── common.h
├── INSTALL.md
├── pool-private.h
├── html-private.h
├── html-find.c
├── css-core.c
├── font-private.h
├── README.md
├── SECURITY.md
├── font-extents.c
├── html-attr.c
├── common.c
├── default.css
├── common-private.h
├── default-css.h
├── font.h
├── css-private.h
├── html-core.c
├── install-sh
├── file.c
├── dict.c
├── html.h
├── htmlcss.svg
├── css-rule.c
├── Makefile.in
├── image.c
├── pool.c
├── sha3.c
├── configure.ac
├── html-node.c
├── css.h
├── LICENSE
├── CONTRIBUTING.md
└── html-load.c
/NOTICE:
--------------------------------------------------------------------------------
1 | HTMLCSS
2 |
3 | Copyright © 2018-2025 by Michael R Sweet
4 |
--------------------------------------------------------------------------------
/.lgtm.yml:
--------------------------------------------------------------------------------
1 | queries:
2 | - exclude: cpp/uncontrolled-allocation-size
3 |
--------------------------------------------------------------------------------
/htmlcss-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/michaelrsweet/htmlcss/HEAD/htmlcss-512.png
--------------------------------------------------------------------------------
/CHANGES.md:
--------------------------------------------------------------------------------
1 | Changes in HTMLCSS
2 | ==================
3 |
4 |
5 | v0.1 - YYYY-MM-DD
6 | -----------------
7 |
8 | - Initial release.
9 |
--------------------------------------------------------------------------------
/htmlcss.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/TODO.md:
--------------------------------------------------------------------------------
1 | To-Do
2 | =====
3 |
4 | - Refactor to use TTF library?
5 | - Add virtual device interface (thinking particularly of using PDFio, but other
6 | devices possible) for managing fonts/widths/rendering.
7 | - Update CSS and HTML code for current standards.
8 |
9 |
--------------------------------------------------------------------------------
/htmlcss.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/htmlcss.pc.in:
--------------------------------------------------------------------------------
1 | prefix=@prefix@
2 | exec_prefix=@exec_prefix@
3 | libdir=@libdir@
4 | includedir=@includedir@
5 |
6 | Name: pdfio
7 | Description: PDF read/write library
8 | Version: @PDFIO_VERSION@
9 | URL: https://www.msweet.org/pdfio
10 | Cflags: @PKGCONFIG_CFLAGS@
11 | Libs: @PKGCONFIG_LIBS@
12 | Libs.private: @PKGCONFIG_LIBS_PRIVATE@
13 | Requires: @PKGCONFIG_REQUIRES@
14 |
--------------------------------------------------------------------------------
/DOCUMENTATION.md:
--------------------------------------------------------------------------------
1 | ---
2 | title: How to Use HTMLCSS
3 | author: Michael R Sweet
4 | copyright: Copyright © 2018-2025 by Michael R Sweet
5 | version: 0.1
6 | ...
7 |
8 | Contents
9 | ========
10 |
11 | [How to Use HTMLCSS](@)
12 |
13 | [Example](@)
14 |
15 | [Reference](@)
16 |
17 |
18 | How to Use HTMLCSS
19 | ==================
20 |
21 |
22 | Example
23 | =======
24 |
25 |
26 | Reference
27 | =========
28 |
--------------------------------------------------------------------------------
/htmlcss.h:
--------------------------------------------------------------------------------
1 | //
2 | // Main header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_H
13 | # define HTMLCSS_H
14 | # include "font.h"
15 | # include "image.h"
16 | # include "html.h"
17 | # include "run.h"
18 | #endif // !HTMLCSS_H
19 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | Code of Conduct
2 | ===============
3 |
4 | My goal is to provide quality open source software that everyone can use.
5 | While I may not be able to address every request or accept every contribution
6 | to this project, I will do my best to develop and maintain it for the common
7 | good. As part of the open source community, I expect everyone to:
8 |
9 | - Be friendly and patient.
10 | - Be respectful, even if we disagree.
11 | - Be honest.
12 | - Be accepting of all people.
13 | - Fully explain your concerns, issues, or ideas.
14 |
--------------------------------------------------------------------------------
/run.h:
--------------------------------------------------------------------------------
1 | //
2 | // Block run header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_RUN_H
13 | # define HTMLCSS_RUN_H
14 | # include "html.h"
15 | # include "css.h"
16 | # ifdef __cplusplus
17 | extern "C" {
18 | # endif // __cplusplus
19 |
20 |
21 | //
22 | // Types...
23 | //
24 |
25 |
26 | //
27 | // Functions...
28 | //
29 |
30 |
31 | # ifdef __cplusplus
32 | }
33 | # endif // __cplusplus
34 | #endif // !HTMLCSS_RUN_H
35 |
--------------------------------------------------------------------------------
/file-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Private file handling header for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_FILE_PRIVATE_H
13 | # define HTMLCSS_FILE_PRIVATE_H
14 | # include "file.h"
15 | # include "pool-private.h"
16 | # include
17 | # ifdef __cplusplus
18 | extern "C" {
19 | # endif // __cplusplus
20 |
21 |
22 | //
23 | // Types...
24 | //
25 |
26 | struct _hc_file_s // High-level file/stream
27 | {
28 | hc_pool_t *pool; // Memory pool
29 | const char *url; // URL or filename
30 | gzFile fp; // File pointer
31 | const _hc_uchar_t *buffer, // String buffer
32 | *bufptr, // Pointer into buffer
33 | *bufend; // End of buffer
34 | int linenum; // Current line number
35 | };
36 |
37 |
38 | //
39 | // Functions...
40 | //
41 |
42 | extern bool _hcFileError(hc_file_t *file, const char *message, ...) _HC_FORMAT_ARGS(2,3);
43 |
44 |
45 | # ifdef __cplusplus
46 | }
47 | # endif // __cplusplus
48 | #endif // !HTMLCSS_FILE_PRIVATE_H
49 |
--------------------------------------------------------------------------------
/image.h:
--------------------------------------------------------------------------------
1 | //
2 | // Image handling header for HTMLCSS library.
3 | //
4 | // Note: The purpose of these functions is currently just to discover the
5 | // dimensions and format of an image file and not to decode or transform the
6 | // contents of an image.
7 | //
8 | // https://github.com/michaelrsweet/htmlcss
9 | //
10 | // Copyright © 2019-2025 by Michael R Sweet.
11 | //
12 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
13 | // information.
14 | //
15 |
16 | #ifndef HTMLCSS_IMAGE_H
17 | # define HTMLCSS_IMAGE_H
18 | # include "css.h"
19 | # ifdef __cplusplus
20 | extern "C" {
21 | # endif // __cplusplus
22 |
23 |
24 | //
25 | // Types...
26 | //
27 |
28 | typedef struct _hc_image_s hc_image_t; // Image object
29 |
30 |
31 | //
32 | // Functions...
33 | //
34 |
35 | extern void hcImageDelete(hc_image_t *image);
36 | extern const char *hcImageGetFormat(hc_image_t *image);
37 | extern int hcImageGetHeight(hc_image_t *image);
38 | extern hc_size_t hcImageGetSize(hc_image_t *image);
39 | extern int hcImageGetWidth(hc_image_t *image);
40 | extern hc_image_t *hcImageNew(hc_pool_t *pool, hc_file_t *file);
41 |
42 |
43 | # ifdef __cplusplus
44 | }
45 | # endif // __cplusplus
46 | #endif // !HTMLCSS_IMAGE_H
47 |
--------------------------------------------------------------------------------
/file.h:
--------------------------------------------------------------------------------
1 | //
2 | // File handling header for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_FILE_H
13 | # define HTMLCSS_FILE_H
14 | # include "pool.h"
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef struct _hc_file_s hc_file_t; // High-level file/stream
25 |
26 |
27 | //
28 | // Functions...
29 | //
30 |
31 | extern void hcFileDelete(hc_file_t *file);
32 | extern int hcFileGetc(hc_file_t *file);
33 | extern hc_file_t *hcFileNewBuffer(hc_pool_t *pool, const void *buffer, size_t bytes);
34 | extern hc_file_t *hcFileNewString(hc_pool_t *pool, const char *s);
35 | extern hc_file_t *hcFileNewURL(hc_pool_t *pool, const char *url, const char *baseurl);
36 | extern size_t hcFileRead(hc_file_t *file, void *buffer, size_t bytes);
37 | extern size_t hcFileSeek(hc_file_t *file, size_t offset);
38 | extern void hcFileUngetc(hc_file_t *file, int ch);
39 |
40 |
41 | # ifdef __cplusplus
42 | }
43 | # endif // __cplusplus
44 | #endif // !HTMLCSS_FILE_H
45 |
--------------------------------------------------------------------------------
/dict.h:
--------------------------------------------------------------------------------
1 | //
2 | // Dictionary header for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_DICT_H
13 | # define HTMLCSS_DICT_H
14 | # include "pool.h"
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef struct _hc_dict_s hc_dict_t;
25 | // Key/value string dictionary
26 |
27 |
28 | //
29 | // Functions...
30 | //
31 |
32 | extern hc_dict_t *hcDictCopy(const hc_dict_t *dict);
33 | extern void hcDictDelete(hc_dict_t *dict);
34 | extern size_t hcDictGetCount(const hc_dict_t *dict);
35 | extern const char *hcDictGetIndexKeyValue(const hc_dict_t *dict, size_t idx, const char **key);
36 | extern const char *hcDictGetKeyValue(const hc_dict_t *dict, const char *key);
37 | extern hc_dict_t *hcDictNew(hc_pool_t *pool);
38 | extern void hcDictRemoveKey(hc_dict_t *dict, const char *key);
39 | extern void hcDictSetKeyValue(hc_dict_t *dict, const char *key, const char *value);
40 |
41 |
42 | # ifdef __cplusplus
43 | }
44 | # endif // __cplusplus
45 | #endif // !HTMLCSS_DICT_H
46 |
--------------------------------------------------------------------------------
/sha3.h:
--------------------------------------------------------------------------------
1 | //
2 | // SHA3 hash definitions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2015-2025 by Michael R Sweet. All rights reserved.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_SHA3_H
13 | # define HTMLCSS_SHA3_H
14 | # include
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Constants...
22 | //
23 |
24 | # define HC_SHA3_256_SIZE 32 // SHA3-256
25 | # define HC_SHA3_512_SIZE 64 // SHA3-512
26 |
27 |
28 | //
29 | // Types...
30 | //
31 |
32 | typedef struct hc_sha3_s // SHA3 hashing context
33 | {
34 | unsigned char used, // Bytes "used" in state
35 | block, // Bytes per block
36 | state[200]; // SHA3 state
37 | } hc_sha3_t;
38 |
39 | typedef unsigned char hc_sha3_256_t[HC_SHA3_256_SIZE];
40 | typedef unsigned char hc_sha3_512_t[HC_SHA3_512_SIZE];
41 |
42 |
43 | //
44 | // Prototypes...
45 | //
46 |
47 | extern void hcSHA3Final(hc_sha3_t *ctx, unsigned char *hash, size_t hashlen);
48 | extern void hcSHA3Init(hc_sha3_t *ctx);
49 | extern void hcSHA3Update(hc_sha3_t *ctx, const void *data, size_t datalen);
50 |
51 |
52 | # ifdef __cplusplus
53 | }
54 | # endif // __cplusplus
55 | #endif // !HTMLCSS_SHA3_H
56 |
--------------------------------------------------------------------------------
/pool.h:
--------------------------------------------------------------------------------
1 | //
2 | // Memory pool header for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_POOL_H
13 | # define HTMLCSS_POOL_H
14 | # include "common.h"
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef struct _hc_pool_s hc_pool_t; // Memory allocation pool
25 |
26 | typedef bool (*hc_error_cb_t)(void *ctx, const char *message, int linenum);
27 | typedef char *(*hc_url_cb_t)(void *ctx, const char *url, char *buffer, size_t bufsize);
28 |
29 |
30 | //
31 | // Functions...
32 | //
33 |
34 | extern void hcPoolDelete(hc_pool_t *pool);
35 | extern const char *hcPoolGetLastError(hc_pool_t *pool);
36 | extern const char *hcPoolGetString(hc_pool_t *pool, const char *s);
37 | extern const char *hcPoolGetURL(hc_pool_t *pool, const char *url, const char *baseurl);
38 | extern hc_pool_t *hcPoolNew(void);
39 | extern void hcPoolSetErrorCallback(hc_pool_t *pool, hc_error_cb_t cb, void *ctx);
40 | extern void hcPoolSetURLCallback(hc_pool_t *pool, hc_url_cb_t cb, void *ctx);
41 |
42 |
43 | # ifdef __cplusplus
44 | }
45 | # endif // __cplusplus
46 | #endif // !HTMLCSS_POOL_H
47 |
--------------------------------------------------------------------------------
/TESTSUITE.md:
--------------------------------------------------------------------------------
1 | Notes About the Test Suite Directory
2 | ====================================
3 |
4 | The "testsuite" directory contains files used by the `testhtmlcss` program to
5 | verify the proper operation of the HTMLCSS library.
6 |
7 |
8 | HTML and CSS Files
9 | ------------------
10 |
11 | Most of the HTML and CSS files are my original creation. Many come from the
12 | HTMLDOC test suite. The "source-sans-pro.css" file is part of the Adobe open
13 | font project - see "Font Files" below.
14 |
15 |
16 | Image Files
17 | -----------
18 |
19 | All of the GIF and JPEG image files are my original creation. The "logo.gif"
20 | file is the original logo of a company I founded and later closed after 17 years
21 | when I went to work for Apple.
22 |
23 | The PNG test images come from Willem van Schaik's
24 | [PngSuite](http://www.schaik.com/pngsuite/). The license for these images can
25 | be found in the file "pngsuite-LICENSE.md".
26 |
27 | The SVG test images come from several sources - see the corresponding
28 | "xxx-LICENSE.md" files.
29 |
30 |
31 | Font Files
32 | ----------
33 |
34 | The test fonts are all from Adobe's open font project. The license for these
35 | fonts can be found in the file "Source-LICENSE.md". The selection of font files
36 | was driven by a need to test selection of different weights and styles from CSS
37 | and to test support for Unicode character ranges - the Japanese regional set is
38 | the smallest...
39 |
--------------------------------------------------------------------------------
/common.h:
--------------------------------------------------------------------------------
1 | //
2 | // Common header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_COMMON_H
13 | # define HTMLCSS_COMMON_H
14 | # include
15 | # include
16 | # include
17 | # include
18 | # include
19 | # include
20 |
21 |
22 | //
23 | // Visibility and other annotations...
24 | //
25 |
26 | # if _WIN32
27 | # define _HC_DEPRECATED(m)
28 | # define _HC_INTERNAL
29 | # define _HC_PRIVATE
30 | # define _HC_PUBLIC
31 | # define _HC_FORMAT(a,b)
32 | # define _HC_NONNULL(...)
33 | # define _HC_NORETURN
34 | # elif defined(__has_extension) || defined(__GNUC__)
35 | # define _HC_DEPRECATED(m) __attribute__ ((deprecated(m))) __attribute__ ((visibility("default")))
36 | # define _HC_INTERNAL __attribute__ ((visibility("hidden")))
37 | # define _HC_PRIVATE __attribute__ ((visibility("default")))
38 | # define _HC_PUBLIC __attribute__ ((visibility("default")))
39 | # define _HC_FORMAT(a,b) __attribute__ ((__format__(__printf__, a,b)))
40 | # define _HC_NONNULL(...) __attribute__ ((nonnull(__VA_ARGS__)))
41 | # define _HC_NORETURN __attribute__ ((noreturn))
42 | # else
43 | # define _HC_DEPRECATED(m)
44 | # define _HC_INTERNAL
45 | # define _HC_PRIVATE
46 | # define _HC_PUBLIC
47 | # define _HC_FORMAT(a,b)
48 | # define _HC_NONNULL(...)
49 | # define _HC_NORETURN
50 | # endif // __has_extension || __GNUC__
51 | #endif // !HTMLCSS_COMMON_H
52 |
--------------------------------------------------------------------------------
/INSTALL.md:
--------------------------------------------------------------------------------
1 | Build Instructions
2 | ==================
3 |
4 | HTMLCSS requires a C99 compiler like GCC, Clang, or Visual C, along with
5 | POSIX-compliant `make` and `sh` programs (Linux/Unix), Visual Studio (Windows),
6 | and/or Xcode (macOS) for doing the build. The only required library is ZLIB
7 | (1.1 or later) for compression support.
8 |
9 |
10 | Getting Prerequisites
11 | ---------------------
12 |
13 | Run the following commands on CentOS/Fedora/RHEL:
14 |
15 | sudo dnf groupinstall 'Development Tools'
16 | sudo dnf install zlib-devel
17 |
18 | Run the following commands on Debian/Raspbian/Ubuntu:
19 |
20 | sudo apt-get install build-essential zlib1g-dev
21 |
22 | Install Xcode from the AppStore on macOS.
23 |
24 | Install Visual Studio 2019 or later on Windows. The Visual Studio solution
25 | "htmlcss.sln" will download all the prerequisite NuGet packages.
26 |
27 |
28 | Building HTMLCSS
29 | ----------------
30 |
31 | HTMLCSS uses the usual `configure` script to generate a `make` file:
32 |
33 | ./configure [options]
34 | make
35 |
36 | Use `./configure --help` to see a full list of options.
37 |
38 | There is also an Xcode project under the `xcode` directory that can be used on
39 | macOS:
40 |
41 | open xcode/htmlcss.xcodeproj
42 |
43 | and a Visual Studio solution under the `vcnet` directory that must be used on
44 | Windows.
45 |
46 | You can test the build by running the HTMLCSS test program:
47 |
48 | make test
49 |
50 |
51 | Installing HTMLCSS
52 | ----------------
53 |
54 | Once you have successfully built HTMLCSS, install it using:
55 |
56 | sudo make install
57 |
58 | By default everything will be installed under `/usr/local`. Use the `--prefix`
59 | configure option to override the base installation directory. Set the
60 | `DESTDIR`, `DSTROOT`, or `RPM_BUILD_ROOT` environment variables to redirect the
61 | installation to a staging area, as is typically done for most software packaging
62 | systems (using one of those environment variables...)
63 |
--------------------------------------------------------------------------------
/pool-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Private memory pool header for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_POOL_PRIVATE_H
13 | # define HTMLCSS_POOL_PRIVATE_H
14 | # include "common-private.h"
15 | # include "dict.h"
16 | # include
17 | # include
18 | # ifdef __cplusplus
19 | extern "C" {
20 | # endif // __cplusplus
21 |
22 |
23 | //
24 | // Types...
25 | //
26 |
27 | typedef struct _hc_font_info_s _hc_font_info_t;
28 | // Font cache information
29 |
30 | struct _hc_pool_s // Memory pool
31 | {
32 | struct lconv *loc; // Locale information
33 | size_t loc_declen; // Length of decimal point
34 |
35 | bool fonts_loaded; // Did we load the fonts?
36 | size_t num_fonts; // Number of fonts in pool
37 | size_t alloc_fonts; // Allocated size of fonts array
38 | _hc_font_info_t *fonts; // Fonts array
39 | size_t font_index[256]; // Index into fonts array
40 |
41 | size_t num_strings; // Number of strings in pool
42 | size_t alloc_strings; // Allocated size of strings array
43 | char **strings; // Strings array
44 |
45 | hc_dict_t *urls; // URLs mapped to local files
46 |
47 | hc_error_cb_t error_cb; // Error callback
48 | void *error_ctx; // Error callback context pointer
49 | char *last_error; // Last error message
50 |
51 | hc_url_cb_t url_cb; // URL callback
52 | void *url_ctx; // URL callback context pointer
53 | };
54 |
55 |
56 | //
57 | // Functions...
58 | //
59 |
60 | extern void _hcPoolDeleteFonts(hc_pool_t *pool);
61 | extern bool _hcPoolError(hc_pool_t *pool, int linenum, const char *message, ...) _HC_FORMAT_ARGS(3, 4);
62 | extern bool _hcPoolErrorv(hc_pool_t *pool, int linenum, const char *message, va_list ap);
63 |
64 |
65 | # ifdef __cplusplus
66 | }
67 | # endif // __cplusplus
68 | #endif // !HTMLCSS_POOL_PRIVATE_H
69 |
--------------------------------------------------------------------------------
/html-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Private HTML header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_HTML_PRIVATE_H
13 | # define HTMLCSS_HTML_PRIVATE_H
14 | # include "html.h"
15 | # include "common-private.h"
16 | # include "dict.h"
17 | # include "pool-private.h"
18 | # ifdef __cplusplus
19 | extern "C" {
20 | # endif // __cplusplus
21 |
22 |
23 | //
24 | // Private types...
25 | //
26 |
27 | struct _hc_node_s
28 | {
29 | hc_element_t element; // Element type
30 | hc_node_t *parent; // Parent node
31 | hc_node_t *prev_sibling; // Previous (sibling) node
32 | hc_node_t *next_sibling; // Next (sibling) node
33 | union
34 | {
35 | char comment[1]; // Comment value
36 | struct
37 | {
38 | hc_node_t *first_child; // First child node
39 | hc_node_t *last_child; // Last child node
40 | hc_dict_t *attrs; // Attributes dictionary
41 | hc_html_t *html; // HTML document
42 | const hc_dict_t *base_props; // Base CSS properties dictionary
43 | } element; // Element value
44 | char string[1]; // String value
45 | char unknown[1]; // Unknown element/directive value
46 | } value; // Node value
47 | };
48 |
49 | struct _hc_html_s
50 | {
51 | hc_pool_t *pool; // Memory pool
52 | hc_css_t *css; // Stylesheet
53 | hc_node_t *root; // Root node
54 | hc_error_cb_t error_cb; // Error callback
55 | void *error_ctx; // Error callback context pointer
56 | hc_url_cb_t url_cb; // URL callback
57 | void *url_ctx; // URL callback context pointer
58 | };
59 |
60 |
61 | //
62 | // Private functions...
63 | //
64 |
65 | extern bool _hcNodeComputeCSSTextFont(hc_node_t *node, const hc_dict_t *props, hc_text_t *text);
66 | extern hc_node_t *_hcNodeNewUnknown(hc_node_t *parent, const char *unk);
67 |
68 |
69 | # ifdef __cplusplus
70 | }
71 | # endif // __cplusplus
72 | #endif // !HTMLCSS_HTML_PRIVATE_H
73 |
--------------------------------------------------------------------------------
/html-find.c:
--------------------------------------------------------------------------------
1 | //
2 | // HTML find functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "html-private.h"
13 |
14 |
15 | //
16 | // Local functions...
17 | //
18 |
19 | static hc_node_t *html_walk_next(hc_node_t *current);
20 |
21 |
22 | //
23 | // 'hcHTMLFindNode()' - Find the first node in a document that matches the given
24 | // element and/or ID string.
25 | //
26 |
27 | hc_node_t * // O - First matching node or `NULL`
28 | hcHTMLFindNode(hc_html_t *html, // I - HTML document
29 | hc_node_t *current, // I - Current node or `NULL`
30 | hc_element_t element, // I - Element or `HC_ELEMENT_WILDCARD` for any
31 | const char *id) // I - ID string or `NULL` for any
32 | {
33 | const char *current_id; // Current ID attribute
34 |
35 |
36 | if (!html)
37 | return (NULL);
38 |
39 | if (!current)
40 | current = html->root;
41 | else
42 | current = html_walk_next(current);
43 |
44 | while (current)
45 | {
46 | if (element == HC_ELEMENT_WILDCARD || current->element == element)
47 | {
48 | if (!id)
49 | break;
50 |
51 | if ((current_id = hcNodeAttrGetNameValue(current, "id")) != NULL && !strcmp(current_id, id))
52 | break;
53 | }
54 |
55 | current = html_walk_next(current);
56 | }
57 |
58 | return (current);
59 | }
60 |
61 |
62 | //
63 | // 'html_walk_next()' - Walk the node tree.
64 | //
65 |
66 | static hc_node_t * // O - Next logical node or `NULL`
67 | html_walk_next(hc_node_t *current) // I - Current node
68 | {
69 | hc_node_t *next; // Next node
70 |
71 |
72 | if ((next = hcNodeGetFirstChildNode(current)) == NULL)
73 | {
74 | if ((next = hcNodeGetNextSiblingNode(current)) == NULL)
75 | {
76 | do
77 | {
78 | next = hcNodeGetParentNode(current);
79 | }
80 | while (next && hcNodeGetNextSiblingNode(next) == NULL);
81 |
82 | next = hcNodeGetNextSiblingNode(next);
83 | }
84 | }
85 |
86 | return (next);
87 | }
88 |
--------------------------------------------------------------------------------
/css-core.c:
--------------------------------------------------------------------------------
1 | //
2 | // CSS import functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | # include "css-private.h"
13 | # include "pool.h"
14 |
15 |
16 | //
17 | // 'hcCSSDelete()' - Free memory associated with a stylesheet.
18 | //
19 |
20 | void
21 | hcCSSDelete(hc_css_t *css) // I - Stylesheet
22 | {
23 | hc_element_t element; // Current element
24 |
25 |
26 | if (!css)
27 | return;
28 |
29 | for (element = HC_ELEMENT_WILDCARD; element < HC_ELEMENT_MAX; element ++)
30 | _hcRuleColClear(css->rules + element, 0);
31 | _hcRuleColClear(&css->all_rules, 1);
32 |
33 | free(css);
34 | }
35 |
36 |
37 | //
38 | // 'hcCSSNew()' - Allocate a new stylesheet.
39 | //
40 |
41 | hc_css_t * // O - Stylesheet
42 | hcCSSNew(hc_pool_t *pool) // I - Memory pool
43 | {
44 | hc_css_t *css = (hc_css_t *)calloc(1, sizeof(hc_css_t));
45 | // Stylesheet
46 |
47 |
48 | if (css)
49 | {
50 | // Set defaults based on "universal" size (intersection of US Letter and
51 | // ISO A4) at 128ppi.
52 | css->pool = pool;
53 |
54 | hcCSSSetMedia(css, "print", 24, 8, 1058.27f, 1408.0f);
55 | }
56 |
57 | return (css);
58 | }
59 |
60 |
61 | //
62 | // 'hcCSSSetMedia()' - Set the base media settings.
63 | //
64 |
65 | int // O - 1 on success, 0 on failure
66 | hcCSSSetMedia(
67 | hc_css_t *css, // I - Stylesheet
68 | const char *type, // I - Media type ("print', etc.)
69 | int color_bits, // I - Bits of color supported
70 | int monochrome_bits, // I - Bits of grayscale supported
71 | float width, // I - Device width
72 | float height) // I - Device height
73 | {
74 | if (!css || !type || color_bits < 0 || monochrome_bits < 0 || width <= 0.0f || height <= 0.0f)
75 | return (0);
76 |
77 | css->media.type = hcPoolGetString(css->pool, type);
78 | css->media.color_bits = color_bits;
79 | css->media.monochrome_bits = monochrome_bits;
80 | css->media.size.width = width;
81 | css->media.size.height = height;
82 |
83 | return (0);
84 | }
85 |
--------------------------------------------------------------------------------
/font-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Private font header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_FONT_PRIVATE_H
13 | # define HTMLCSS_FONT_PRIVATE_H
14 | # include "common-private.h"
15 | # include "font.h"
16 | # ifdef __cplusplus
17 | extern "C" {
18 | # endif // __cplusplus
19 |
20 |
21 | //
22 | // Constants...
23 | //
24 |
25 | # define _HC_FONT_MAX_CHAR 262144
26 | # define _HC_FONT_MAX_GROUPS 65536 // Maximum number of sub-groups
27 | # define _HC_FONT_MAX_NAMES 16777216// Maximum size of names table we support
28 |
29 |
30 | //
31 | // Types...
32 | //
33 |
34 | typedef struct _hc_font_metric_s // Font metric information
35 | {
36 | short width, // Advance width
37 | left_bearing; // Left side bearing
38 | } _hc_font_metric_t;
39 |
40 | struct _hc_font_s
41 | {
42 | hc_pool_t *pool; // Memory pool
43 | size_t idx; // Font number in file
44 | size_t num_fonts; // Number of fonts in this file
45 | const char *copyright; // Copyright string
46 | const char *family; // Font family string
47 | const char *postscript_name;
48 | // PostScript name string
49 | const char *version; // Font version string
50 | bool is_fixed; // Is this a fixed-width font?
51 | int max_char, // Last character in font
52 | min_char; // First character in font
53 | size_t num_cmap; // Number of entries in glyph map
54 | int *cmap; // Unicode character to glyph map
55 | _hc_font_metric_t *widths[_HC_FONT_MAX_CHAR / 256];
56 | // Character metrics (sparse array)
57 | float units; // Width units
58 | short ascent, // Maximum ascent above baseline
59 | descent, // Maximum descent below baseline
60 | cap_height, // "A" height
61 | x_height, // "x" height
62 | x_max, // Bounding box
63 | x_min,
64 | y_max,
65 | y_min,
66 | weight; // Font weight
67 | float italic_angle; // Angle of italic text
68 | hc_font_stretch_t stretch; // Font stretch value
69 | hc_font_style_t style; // Font style
70 | };
71 |
72 |
73 | # ifdef __cplusplus
74 | }
75 | # endif // __cplusplus
76 | #endif // !HTMLCSS_FONT_PRIVATE_H
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | HTMLCSS - Lightweight HTML/CSS Library
2 | ======================================
3 |
4 | 
5 | 
6 | [](https://github.com/michaelrsweet/htmlcss/actions/workflows/build.yml)
7 | [](https://scan.coverity.com/projects/michaelrsweet-htmlcss)
8 |
9 | HTMLCSS is a lightweight HTML/CSS parser written in C that allows applications
10 | to prepare a HTML document for rendering or conversion. Features include:
11 |
12 | - HTML 5 markup parser
13 | - CSS 3 stylesheet parser
14 | - GIF/JPEG/PNG image file parser (metadata only)
15 | - Functions to calculate CSS properties for a given node in a HTML document
16 | - Functions to extract HTML "runs" consisting of CSS properties, content
17 | strings, and image references that can be rendered directly, including the
18 | :before and :after content from a stylesheet
19 | - A device API for reporting capabilities and handling fonts
20 | - A memory pool for efficiently storing and caching strings and CSS properties
21 | - A URL API to support fetching remote resources
22 |
23 | HTMLCSS does *not* support dynamic HTML content created using Javascript in a
24 | HTML document, as such content is typically used for interactive/dynamic web
25 | pages while HTMLCSS is intended for use with static content.
26 |
27 |
28 | Requirements
29 | ------------
30 |
31 | HTMLCSS requires a C99 compiler like GCC, Clang, or Visual C, along with
32 | POSIX-compliant `make` and `sh` programs (Linux/Unix), Visual Studio (Windows),
33 | and/or Xcode (macOS) for doing the build. The only required library is ZLIB
34 | (1.1 or later) for compression support.
35 |
36 |
37 | Dcumentation and Examples
38 | -------------------------
39 |
40 | Documentation can be found in the "htmlcss.html" file.
41 |
42 |
43 | Contributing Code
44 | -----------------
45 |
46 | Code contributions should be submitted as pull requests on the Github site:
47 |
48 | http://github.com/michaelrsweet/htmlcss/pulls
49 |
50 | See the file "CONTRIBUTING.md" for more details.
51 |
52 |
53 | Legal Stuff
54 | -----------
55 |
56 | HTMLCSS is Copyright © 2018-2025 by Michael R Sweet.
57 |
58 | HTMLCSS is licensed under the Apache License Version 2.0. See the files
59 | "LICENSE" and "NOTICE" for more information.
60 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 | Security Policy
2 | ===============
3 |
4 | This file describes how security issues are reported and handled, and what the
5 | expectations are for security issues reported to this project.
6 |
7 |
8 | Reporting a Security Bug
9 | ------------------------
10 |
11 | For the purposes of this project, a security bug is a software defect that
12 | allows a *local or remote user* to gain unauthorized access or privileges on the
13 | host computer. Such defects should be reported to the project security advisory
14 | page at .
15 |
16 | > *Note:* If you've found a software defect that allows a *program* to gain
17 | > unauthorized access or privileges on the host computer or causes the program
18 | > to crash, that defect should be reported as an ordinary project issue at
19 | > .
20 |
21 |
22 | Responsible Disclosure
23 | ----------------------
24 |
25 | With *responsible disclosure*, a security issue (and its fix) is disclosed only
26 | after a mutually-agreed period of time (the "embargo date"). The issue and fix
27 | are shared amongst and reviewed by the key stakeholders (Linux distributions,
28 | OS vendors, etc.) and the CERT/CC. Fixes are released to the public on the
29 | agreed-upon date.
30 |
31 | > Responsible disclosure applies only to production releases. A security
32 | > vulnerability that only affects unreleased code can be fixed immediately
33 | > without coordination. Vendors *should not* package and release unstable
34 | > snapshots, beta releases, or release candidates of this software.
35 |
36 |
37 | Supported Versions
38 | ------------------
39 |
40 | All production releases of this software are subject to this security policy. A
41 | production release is tagged and given a semantic version number of the form:
42 |
43 | MAJOR.MINOR.PATCH
44 |
45 | where "MAJOR" is an integer starting at 1 and "MINOR" and "PATCH" are integers
46 | starting at 0. A feature release has a "PATCH" value of 0, for example:
47 |
48 | 1.0.0
49 | 1.1.0
50 | 2.0.0
51 |
52 | Beta releases and release candidates are *not* production releases and use
53 | semantic version numbers of the form:
54 |
55 | MAJOR.MINORbNUMBER
56 | MAJOR.MINORrcNUMBER
57 |
58 | where "MAJOR" and "MINOR" identify the new feature release version number and
59 | "NUMBER" identifies a beta or release candidate number starting at 1, for
60 | example:
61 |
62 | 1.0b1
63 | 1.0b2
64 | 1.0rc1
65 |
--------------------------------------------------------------------------------
/font-extents.c:
--------------------------------------------------------------------------------
1 | //
2 | // Font extents function for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "font-private.h"
13 |
14 |
15 | //
16 | // 'hcFontComputeExtents()' - Compute the extents of a string when rendered using the
17 | // given font object, size, style, etc.
18 | //
19 |
20 | int // O - 1 on success, 0 on failure
21 | hcFontComputeExtents(
22 | const hc_font_t *font, // I - Font object
23 | float size, // I - Font size
24 | const char *s, // I - String
25 | hc_rect_t *extents) // O - Extents of the string
26 | {
27 | int ch, // Current character
28 | first = 1, // First character
29 | width = 0; // Width
30 | _hc_font_metric_t *widths; // Widths
31 |
32 |
33 | _HC_DEBUG("hcFontComputeExtents(font=%p, size=%.2f, s=\"%s\", extents=%p)\n", (void *)font, size, s, (void *)extents);
34 |
35 | if (extents)
36 | memset(extents, 0, sizeof(hc_rect_t));
37 |
38 | if (!font || size <= 0.0f || !s || !extents)
39 | return (0);
40 |
41 | while (*s)
42 | {
43 | if ((*s & 0xe0) == 0xc0 && (s[1] & 0xc0) == 0x80)
44 | {
45 | ch = ((*s & 0x1f) << 6) | (s[1] & 0x3f);
46 | s += 2;
47 | }
48 | else if ((*s & 0xf0) == 0xe0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80)
49 | {
50 | ch = ((*s & 0x0f) << 12) | ((s[1] & 0x3f) << 6) | (s[2] & 0x3f);
51 | s += 3;
52 | }
53 | else if ((*s & 0xf8) == 0xf0 && (s[1] & 0xc0) == 0x80 && (s[2] & 0xc0) == 0x80 && (s[3] & 0xc0) == 0x80)
54 | {
55 | ch = ((*s & 0x07) << 18) | ((s[1] & 0x3f) << 12) | ((s[2] & 0x3f) << 6) | (s[3] & 0x3f);
56 | s += 4;
57 | }
58 | else if (*s & 0x80)
59 | return (0);
60 | else
61 | ch = *s++;
62 |
63 | if ((widths = font->widths[ch / 256]) != NULL)
64 | {
65 | if (first)
66 | {
67 | extents->left = -widths[ch & 255].left_bearing / font->units;
68 | first = 0;
69 | }
70 |
71 | width += widths[ch & 255].width;
72 | }
73 | }
74 |
75 | _HC_DEBUG("hcFontComputeExtents: width=%d\n", width);
76 |
77 | extents->bottom = size * font->y_min / font->units;
78 | extents->right = size * width / font->units + extents->left;
79 | extents->top = size * font->y_max / font->units;
80 |
81 | return (1);
82 | }
83 |
--------------------------------------------------------------------------------
/html-attr.c:
--------------------------------------------------------------------------------
1 | //
2 | // HTML attribute functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "html-private.h"
13 |
14 |
15 | //
16 | // 'hcNodeAttrGetCount()' - Get the number of attributes for an element.
17 | //
18 |
19 | size_t // O - Number of attributes
20 | hcNodeAttrGetCount(hc_node_t *node) // I - Element node
21 | {
22 | if (!node || node->element < HC_ELEMENT_DOCTYPE)
23 | return (0);
24 | else
25 | return (hcDictGetCount(node->value.element.attrs));
26 | }
27 |
28 |
29 | //
30 | // 'hcNodeAttrGetIndexNameValue()' - Get the name and value of a specified attribute.
31 | //
32 |
33 | const char * // O - Attribute value or `NULL`
34 | hcNodeAttrGetIndexNameValue(
35 | hc_node_t *node, // I - Element node
36 | size_t idx, // I - Attribute index (0-based)
37 | const char **name) // O - Attribute name
38 | {
39 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name)
40 | return (NULL);
41 |
42 | return (hcDictGetIndexKeyValue(node->value.element.attrs, idx, name));
43 | }
44 |
45 |
46 | //
47 | // 'hcNodeAttrGetNameValue()' - Get the value of an element attribute.
48 | //
49 |
50 | const char * // O - Value or `NULL` if not present
51 | hcNodeAttrGetNameValue(
52 | hc_node_t *node, // I - Element node
53 | const char *name) // I - Attribute name
54 | {
55 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name)
56 | return (NULL);
57 |
58 | return (hcDictGetKeyValue(node->value.element.attrs, name));
59 | }
60 |
61 |
62 | //
63 | // 'hcNodeAttrRemove()' - Delete an element attribute.
64 | //
65 |
66 | void
67 | hcNodeAttrRemove(hc_node_t *node, // I - Element node
68 | const char *name) // I - Attribute name
69 | {
70 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name)
71 | return;
72 |
73 | hcDictRemoveKey(node->value.element.attrs, name);
74 | }
75 |
76 |
77 | //
78 | // 'hcNodeAttrSetNameValue()' - Add an element attribute.
79 | //
80 |
81 | void
82 | hcNodeAttrSetNameValue(
83 | hc_node_t *node, // I - Element node
84 | const char *name, // I - Attribute name
85 | const char *value) // I - Attribute value
86 | {
87 | if (!node || node->element < HC_ELEMENT_DOCTYPE || !name || !value)
88 | return;
89 |
90 | if (!node->value.element.attrs)
91 | node->value.element.attrs = hcDictNew(node->value.element.html->pool);
92 |
93 | hcDictSetKeyValue(node->value.element.attrs, name, value);
94 | }
95 |
--------------------------------------------------------------------------------
/common.c:
--------------------------------------------------------------------------------
1 | //
2 | // Common functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "common-private.h"
13 |
14 |
15 | //
16 | // '_hcDefaultErrorCB()' - Default error callback.
17 | //
18 |
19 | bool // O - `true` to continue, `false` to stop
20 | _hcDefaultErrorCB(
21 | void *ctx, // I - Context pointer (unused)
22 | const char *message, // I - Message string
23 | int linenum) // I - Line number (unused)
24 | {
25 | (void)ctx;
26 | (void)linenum;
27 |
28 | fputs(message, stderr);
29 | putc('\n', stderr);
30 |
31 | return (true);
32 | }
33 |
34 |
35 | //
36 | // '_hcDefaultURLCB()' - Default URL callback.
37 | //
38 |
39 | char * // O - Local path to URL or `NULL`
40 | _hcDefaultURLCB(
41 | void *ctx, // I - Context pointer (unused)
42 | const char *url, // I - URL or filename
43 | char *buffer, // I - Filename buffer
44 | size_t bufsize) // I - Size of filename buffer
45 | {
46 | (void)ctx;
47 |
48 | if (!access(url, R_OK))
49 | {
50 | /*
51 | * Local file we can read...
52 | */
53 |
54 | strncpy(buffer, url, bufsize - 1);
55 | buffer[bufsize - 1] = '\0';
56 | }
57 | else if (!strncmp(url, "file:///", 8))
58 | {
59 | char *bufptr, // Pointer into buffer
60 | *bufend; // End of buffer
61 |
62 | for (url += 7, bufptr = buffer, bufend = buffer + bufsize - 1; *url; url ++)
63 | {
64 | int ch = *url; // Current character
65 |
66 | if (ch == '%' && isxdigit(url[1] & 255) && isxdigit(url[2] & 255))
67 | {
68 | /*
69 | * Percent-escaped character in URL...
70 | */
71 |
72 | if (isdigit(url[1]))
73 | ch = (url[1] - '0') << 4;
74 | else
75 | ch = (tolower(url[1]) - 'a' + 10) << 4;
76 |
77 | if (isdigit(url[2]))
78 | ch |= (url[2] - '0');
79 | else
80 | ch |= (tolower(url[2]) - 'a' + 10);
81 |
82 | url += 2;
83 | }
84 |
85 | if (bufptr < bufend)
86 | {
87 | *bufptr++ = (char)ch;
88 | }
89 | else
90 | {
91 | errno = E2BIG;
92 | *buffer = '\0';
93 | return (NULL);
94 | }
95 | }
96 |
97 | *bufptr = '\0';
98 |
99 | if (access(buffer, R_OK))
100 | {
101 | /*
102 | * File not readable/found...
103 | */
104 |
105 | *buffer = '\0';
106 | return (NULL);
107 | }
108 | }
109 | else
110 | {
111 | errno = EINVAL;
112 | *buffer = '\0';
113 | return (NULL);
114 | }
115 |
116 | return (buffer);
117 | }
118 |
--------------------------------------------------------------------------------
/default.css:
--------------------------------------------------------------------------------
1 | html, address,
2 | blockquote,
3 | body, dd, div,
4 | dl, dt, fieldset, form,
5 | frame, frameset,
6 | h1, h2, h3, h4,
7 | h5, h6, noframes,
8 | ol, p, ul, center,
9 | dir, hr, menu, pre { display: block; unicode-bidi: embed; }
10 | li { display: list-item; }
11 | head { display: none; }
12 | table { display: table; }
13 | tr { display: table-row; }
14 | thead { display: table-header-group; }
15 | tbody { display: table-row-group; }
16 | tfoot { display: table-footer-group; }
17 | col { display: table-column; }
18 | colgroup { display: table-column-group; }
19 | td, th { display: table-cell; }
20 | caption { display: table-caption; }
21 | th { font-weight: bolder; text-align: center; }
22 | caption { text-align: center; }
23 | body { margin: 8px; }
24 | h1 { font-size: 2em; margin: .67em 0; }
25 | h2 { font-size: 1.5em; margin: .75em 0; }
26 | h3 { font-size: 1.17em; margin: .83em 0; }
27 | h4, p,
28 | blockquote, ul,
29 | fieldset, form,
30 | ol, dl, dir,
31 | menu { margin: 1.12em 0; }
32 | h5 { font-size: .83em; margin: 1.5em 0; }
33 | h6 { font-size: .75em; margin: 1.67em 0; }
34 | h1, h2, h3, h4,
35 | h5, h6, b,
36 | strong { font-weight: bolder; }
37 | blockquote { margin-left: 40px; margin-right: 40px; }
38 | i, cite, em,
39 | var, address { font-style: italic; }
40 | pre, tt, code,
41 | kbd, samp { font-family: monospace; }
42 | pre { white-space: pre; }
43 | button, textarea,
44 | input, select { display: inline-block; }
45 | big { font-size: 1.17em; }
46 | small, sub, sup { font-size: .83em; }
47 | sub { vertical-align: sub; }
48 | sup { vertical-align: super; }
49 | table { border-spacing: 2px; }
50 | thead, tbody,
51 | tfoot { vertical-align: middle; }
52 | td, th, tr { vertical-align: inherit; }
53 | s, strike, del { text-decoration: line-through; }
54 | hr { border: 1px inset; }
55 | ol, ul, dir,
56 | menu, dd { margin-left: 40px; }
57 | ol { list-style-type: decimal; }
58 | ol ul, ul ol,
59 | ul ul, ol ol { margin-top: 0; margin-bottom: 0; }
60 | u, ins { text-decoration: underline; }
61 | br:before { content: "\A"; white-space: pre-line; }
62 | center { text-align: center; }
63 | :link, :visited { text-decoration: underline; }
64 | :focus { outline: thin dotted invert; }
65 |
66 | /* Begin bidirectionality settings (do not change) */
67 | /*BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override; }
68 | BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override; }*/
69 |
70 | *[DIR="ltr"] { direction: ltr; unicode-bidi: embed; }
71 | *[DIR="rtl"] { direction: rtl; unicode-bidi: embed; }
72 |
73 | @media print {
74 | h1 { page-break-before: always; }
75 | h1, h2, h3,
76 | h4, h5, h6 { page-break-after: avoid; }
77 | ul, ol, dl { page-break-before: avoid; }
78 | }
79 |
--------------------------------------------------------------------------------
/common-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Common private header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_COMMON_PRIVATE_H
13 | # define HTMLCSS_COMMON_PRIVATE_H
14 | # include "common.h"
15 | # ifdef _WIN32
16 | # include
17 | # include
18 |
19 | //
20 | // Microsoft renames the POSIX functions to _name, and introduces
21 | // a broken compatibility layer using the original names. As a result,
22 | // random crashes can occur when, for example, strdup() allocates memory
23 | // from a different heap than used by malloc() and free().
24 | //
25 | // To avoid moronic problems like this, we #define the POSIX function
26 | // names to the corresponding non-standard Microsoft names.
27 | //
28 |
29 | # define access _access
30 | # define close _close
31 | # define fileno _fileno
32 | # define lseek _lseek
33 | # define mkdir(d,p) _mkdir(d)
34 | # define open _open
35 | # define read _read
36 | # define rmdir _rmdir
37 | # define snprintf _snprintf
38 | # define strdup _strdup
39 | # define unlink _unlink
40 | # define vsnprintf _vsnprintf
41 | # define write _write
42 |
43 | //
44 | // Map various parameters for POSIX...
45 | //
46 |
47 | # define F_OK 00
48 | # define W_OK 02
49 | # define R_OK 04
50 | # define O_RDONLY _O_RDONLY
51 | # define O_WRONLY _O_WRONLY
52 | # define O_CREAT _O_CREAT
53 | # define O_TRUNC _O_TRUNC
54 |
55 | # else
56 | # include
57 | # endif // _WIN32
58 |
59 |
60 | //
61 | // DEBUG is typically defined for debug builds. _HC_DEBUG maps to fprintf when
62 | // DEBUG is defined and is a no-op otherwise...
63 | //
64 |
65 | # ifdef DEBUG
66 | # define _HC_DEBUG(...) fprintf(stderr, __VA_ARGS__)
67 | # else
68 | # define _HC_DEBUG(...)
69 | # endif // DEBUG
70 |
71 |
72 | //
73 | // _HC_FORMAT_ARGS tells the compiler to validate printf-style format
74 | // arguments, producing warnings when there are issues...
75 | //
76 |
77 | # if defined(__has_extension) || defined(__GNUC__)
78 | # define _HC_FORMAT_ARGS(a,b) __attribute__ ((__format__(__printf__, a, b)))
79 | # else
80 | # define _HC_FORMAT_ARGS(a,b)
81 | # endif // __has_extension || __GNUC__
82 |
83 |
84 | # ifdef __cplusplus
85 | extern "C" {
86 | # endif // __cplusplus
87 |
88 |
89 | //
90 | // Types...
91 | //
92 |
93 | typedef int (*_hc_compare_func_t)(const void *, const void *);
94 | // bsearch/qsort comparison function
95 | typedef unsigned char _hc_uchar_t; // Unsigned 8-bit byte
96 |
97 |
98 | //
99 | // Functions...
100 | //
101 |
102 | extern bool _hcDefaultErrorCB(void *ctx, const char *message, int linenum);
103 | extern char *_hcDefaultURLCB(void *ctx, const char *url, char *buffer, size_t bufsize);
104 |
105 |
106 | # ifdef __cplusplus
107 | }
108 | # endif // __cplusplus
109 | #endif // !HTMLCSS_COMMON_PRIVATE_H
110 |
--------------------------------------------------------------------------------
/default-css.h:
--------------------------------------------------------------------------------
1 | static const char *default_css =
2 | "html, address,\n"
3 | "blockquote,\n"
4 | "body, dd, div,\n"
5 | "dl, dt, fieldset, form,\n"
6 | "frame, frameset,\n"
7 | "h1, h2, h3, h4,\n"
8 | "h5, h6, noframes,\n"
9 | "ol, p, ul, center,\n"
10 | "dir, hr, menu, pre { display: block; unicode-bidi: embed; }\n"
11 | "li { display: list-item; }\n"
12 | "head { display: none; }\n"
13 | "table { display: table; }\n"
14 | "tr { display: table-row; }\n"
15 | "thead { display: table-header-group; }\n"
16 | "tbody { display: table-row-group; }\n"
17 | "tfoot { display: table-footer-group; }\n"
18 | "col { display: table-column; }\n"
19 | "colgroup { display: table-column-group; }\n"
20 | "td, th { display: table-cell; }\n"
21 | "caption { display: table-caption; }\n"
22 | "th { font-weight: bolder; text-align: center; }\n"
23 | "caption { text-align: center; }\n"
24 | "body { margin: 8px; }\n"
25 | "h1 { font-size: 2em; margin: .67em 0; }\n"
26 | "h2 { font-size: 1.5em; margin: .75em 0; }\n"
27 | "h3 { font-size: 1.17em; margin: .83em 0; }\n"
28 | "h4, p,\n"
29 | "blockquote, ul,\n"
30 | "fieldset, form,\n"
31 | "ol, dl, dir,\n"
32 | "menu { margin: 1.12em 0; }\n"
33 | "h5 { font-size: .83em; margin: 1.5em 0; }\n"
34 | "h6 { font-size: .75em; margin: 1.67em 0; }\n"
35 | "h1, h2, h3, h4,\n"
36 | "h5, h6, b,\n"
37 | "strong { font-weight: bolder; }\n"
38 | "blockquote { margin-left: 40px; margin-right: 40px; }\n"
39 | "i, cite, em,\n"
40 | "var, address { font-style: italic; }\n"
41 | "pre, tt, code,\n"
42 | "kbd, samp { font-family: monospace; }\n"
43 | "pre { white-space: pre; }\n"
44 | "button, textarea,\n"
45 | "input, select { display: inline-block; }\n"
46 | "big { font-size: 1.17em; }\n"
47 | "small, sub, sup { font-size: .83em; }\n"
48 | "sub { vertical-align: sub; }\n"
49 | "sup { vertical-align: super; }\n"
50 | "table { border-spacing: 2px; }\n"
51 | "thead, tbody,\n"
52 | "tfoot { vertical-align: middle; }\n"
53 | "td, th, tr { vertical-align: inherit; }\n"
54 | "s, strike, del { text-decoration: line-through; }\n"
55 | "hr { border: 1px inset; }\n"
56 | "ol, ul, dir,\n"
57 | "menu, dd { margin-left: 40px; }\n"
58 | "ol { list-style-type: decimal; }\n"
59 | "ol ul, ul ol,\n"
60 | "ul ul, ol ol { margin-top: 0; margin-bottom: 0; }\n"
61 | "u, ins { text-decoration: underline; }\n"
62 | "br:before { content: \"\\A\"; white-space: pre-line; }\n"
63 | "center { text-align: center; }\n"
64 | ":link, :visited { text-decoration: underline; }\n"
65 | ":focus { outline: thin dotted invert; }\n"
66 | "\n"
67 | "/* Begin bidirectionality settings (do not change) */\n"
68 | "/*BDO[DIR=\"ltr\"] { direction: ltr; unicode-bidi: bidi-override; }\n"
69 | "BDO[DIR=\"rtl\"] { direction: rtl; unicode-bidi: bidi-override; }*/\n"
70 | "\n"
71 | "*[DIR=\"ltr\"] { direction: ltr; unicode-bidi: embed; }\n"
72 | "*[DIR=\"rtl\"] { direction: rtl; unicode-bidi: embed; }\n"
73 | "\n"
74 | "@media print {\n"
75 | " h1 { page-break-before: always; }\n"
76 | " h1, h2, h3,\n"
77 | " h4, h5, h6 { page-break-after: avoid; }\n"
78 | " ul, ol, dl { page-break-before: avoid; }\n"
79 | "}\n"
80 | ;
81 |
--------------------------------------------------------------------------------
/font.h:
--------------------------------------------------------------------------------
1 | //
2 | // Font header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_FONT_H
13 | # define HTMLCSS_FONT_H
14 | # include "file.h"
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef enum // Font stretch
25 | {
26 | HC_FONT_STRETCH_NORMAL, // normal
27 | HC_FONT_STRETCH_ULTRA_CONDENSED, // ultra-condensed
28 | HC_FONT_STRETCH_EXTRA_CONDENSED, // extra-condensed
29 | HC_FONT_STRETCH_CONDENSED, // condensed
30 | HC_FONT_STRETCH_SEMI_CONDENSED, // semi-condensed
31 | HC_FONT_STRETCH_SEMI_EXPANDED, // semi-expanded
32 | HC_FONT_STRETCH_EXPANDED, // expanded
33 | HC_FONT_STRETCH_EXTRA_EXPANDED, // extra-expanded
34 | HC_FONT_STRETCH_ULTRA_EXPANDED // ultra-expanded
35 | } hc_font_stretch_t;
36 |
37 | typedef enum // Font style
38 | {
39 | HC_FONT_STYLE_NORMAL, // Normal font
40 | HC_FONT_STYLE_ITALIC, // Italic font
41 | HC_FONT_STYLE_OBLIQUE // Oblique (angled) font
42 | } hc_font_style_t;
43 |
44 | typedef enum // Font variant
45 | {
46 | HC_FONT_VARIANT_NORMAL, // Normal font
47 | HC_FONT_VARIANT_SMALL_CAPS // Font whose lowercase letters are small capitals
48 | } hc_font_variant_t;
49 |
50 | typedef enum // Font weight
51 | {
52 | HC_FONT_WEIGHT_NORMAL, // Normal weight, nominally 400
53 | HC_FONT_WEIGHT_BOLD, // Bold weight, nominally 700
54 | HC_FONT_WEIGHT_BOLDER, // Bolder
55 | HC_FONT_WEIGHT_LIGHTER, // Lighter
56 | HC_FONT_WEIGHT_100 = 100, // Weight 100 (Thin)
57 | HC_FONT_WEIGHT_200 = 200, // Weight 200 (Extra/Ultra-Light)
58 | HC_FONT_WEIGHT_300 = 300, // Weight 300 (Light)
59 | HC_FONT_WEIGHT_400 = 400, // Weight 400 (Normal/Regular)
60 | HC_FONT_WEIGHT_500 = 500, // Weight 500 (Medium)
61 | HC_FONT_WEIGHT_600 = 600, // Weight 600 (Semi/Demi-Bold)
62 | HC_FONT_WEIGHT_700 = 700, // Weight 700 (Bold)
63 | HC_FONT_WEIGHT_800 = 800, // Weight 800 (Extra/Ultra-Bold)
64 | HC_FONT_WEIGHT_900 = 900 // Weight 900 (Black/Heavy)
65 | } hc_font_weight_t;
66 |
67 | typedef struct _hc_font_s hc_font_t; // Font object
68 |
69 | typedef struct hc_rect_s // Rectangle
70 | {
71 | float left; // Left offset
72 | float top; // Top offset
73 | float right; // Right offset
74 | float bottom; // Bottom offset
75 | } hc_rect_t;
76 |
77 |
78 | //
79 | // Functions...
80 | //
81 |
82 | extern void hcFontAddCached(hc_pool_t *pool, hc_font_t *font, const char *url);
83 | extern int hcFontComputeExtents(const hc_font_t *font, float size, const char *s, hc_rect_t *extents);
84 | extern void hcFontDelete(hc_font_t *font);
85 | extern hc_font_t *hcFontFindCached(hc_pool_t *pool, const char *family, hc_font_stretch_t stretch, hc_font_style_t style, hc_font_variant_t variant, hc_font_weight_t weight);
86 | extern int hcFontGetAscent(hc_font_t *font);
87 | extern hc_rect_t *hcFontGetBounds(hc_font_t *font, hc_rect_t *bounds);
88 | extern hc_font_t *hcFontGetCached(hc_pool_t *pool, size_t idx);
89 | extern size_t hcFontGetCachedCount(hc_pool_t *pool);
90 | extern int hcFontGetCapHeight(hc_font_t *font);
91 | extern const int *hcFontGetCMap(hc_font_t *font, size_t *num_cmap);
92 | extern const char *hcFontGetCopyright(hc_font_t *font);
93 | extern int hcFontGetDescent(hc_font_t *font);
94 | extern hc_rect_t *hcFontGetExtents(hc_font_t *font, float size, const char *s, hc_rect_t *extents);
95 | extern const char *hcFontGetFamily(hc_font_t *font);
96 | extern size_t hcFontGetNumFonts(hc_font_t *font);
97 | extern const char *hcFontGetPostScriptName(hc_font_t *font);
98 | extern hc_font_style_t hcFontGetStyle(hc_font_t *font);
99 | extern const char *hcFontGetVersion(hc_font_t *font);
100 | extern hc_font_weight_t hcFontGetWeight(hc_font_t *font);
101 | extern int hcFontGetWidth(hc_font_t *font, int ch);
102 | extern int hcFontGetXHeight(hc_font_t *font);
103 | extern bool hcFontIsFixedPitch(hc_font_t *font);
104 | extern hc_font_t *hcFontNew(hc_pool_t *pool, hc_file_t *file, size_t idx);
105 |
106 |
107 | # ifdef __cplusplus
108 | }
109 | # endif // __cplusplus
110 | #endif // !HTMLCSS_FONT_H
111 |
--------------------------------------------------------------------------------
/css-private.h:
--------------------------------------------------------------------------------
1 | //
2 | // Private CSS header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_CSS_PRIVATE_H
13 | # define HTMLCSS_CSS_PRIVATE_H
14 | # include "css.h"
15 | # include "html-private.h"
16 | # include "sha3.h"
17 | # ifdef __cplusplus
18 | extern "C" {
19 | # endif // __cplusplus
20 |
21 |
22 | //
23 | // Types...
24 | //
25 |
26 | //
27 | // CSS selectors are linked lists starting at the leaf node to speed lookups.
28 | // Each selector is a sequence of matching statements starting with an
29 | // associated element (* wildcard, P, etc.) followed by zero or more additional
30 | // matching statements (".classname", "#identifier", ":link", etc.)
31 | //
32 | // A list of selectors is associated with an array of properties, each of which
33 | // is a simple key/value pair. This association is called a rule set.
34 | //
35 | // For convenience and lookup efficiency, rule sets with compound selectors,
36 | // e.g.:
37 | //
38 | // h1, h2, h3 { font-weight: bold; }
39 | //
40 | // are split into three separate rule sets using each of the selector lists.
41 | //
42 |
43 | typedef enum _hc_match_e
44 | {
45 | _HC_MATCH_ATTR_EXIST, // [NAME]
46 | _HC_MATCH_ATTR_EQUALS, // [NAME=VALUE]
47 | _HC_MATCH_ATTR_CONTAINS, // [NAME*=VALUE]
48 | _HC_MATCH_ATTR_BEGINS, // [NAME^=VALUE]
49 | _HC_MATCH_ATTR_ENDS, // [NAME$=VALUE]
50 | _HC_MATCH_ATTR_LANG, // [NAME|=VALUE] (language/prefix match)
51 | _HC_MATCH_ATTR_SPACE, // [NAME~=VALUE] (space-delimited value match)
52 | _HC_MATCH_CLASS, // .NAME
53 | _HC_MATCH_ID, // #NAME
54 | _HC_MATCH_PSEUDO_CLASS // :NAME or :NANE(VALUE) pseudo-class
55 | } _hc_match_t;
56 |
57 | typedef enum _hc_relation_e // Relationship to previous selector
58 | {
59 | _HC_RELATION_CHILD, // Child (descendent) of previous (E F)
60 | _HC_RELATION_IMMED_CHILD, // Immediate child of previous (E > F)
61 | _HC_RELATION_SIBLING, // Sibling of previous (E ~ F)
62 | _HC_RELATION_IMMED_SIBLING // Immediate sibling of previous (E + F)
63 | } _hc_relation_t;
64 |
65 | typedef struct _hc_css_selstmt_s // CSS selector matching statements
66 | {
67 | _hc_match_t match; // Matching rule
68 | const char *name, // Name, if needed
69 | *value; // Value, if needed
70 | } _hc_css_selstmt_t;
71 |
72 | typedef struct _hc_css_sel_s // CSS selector
73 | {
74 | struct _hc_css_sel_s *prev; // Previous selector
75 | hc_element_t element; // Element
76 | _hc_relation_t relation; // Relation to previous
77 | size_t num_stmts; // Number of selector matching statements
78 | _hc_css_selstmt_t *stmts; // Matching statements
79 | } _hc_css_sel_t;
80 |
81 | typedef struct _hc_rule_s // CSS rule set
82 | {
83 | hc_sha3_256_t hash; // Hash of selector
84 | _hc_css_sel_t *sel; // Leaf selector
85 | hc_dict_t *props; // Properties
86 | } _hc_rule_t;
87 |
88 | typedef struct _hc_rulecol_s // Collection of rules
89 | {
90 | int needs_sort; // Needs sorting?
91 | size_t alloc_rules; // Allocated rules
92 | size_t num_rules; // Number of rules
93 | _hc_rule_t **rules; // Rules
94 | } _hc_rulecol_t;
95 |
96 | struct _hc_css_s
97 | {
98 | hc_pool_t *pool; // Memory pool
99 | hc_media_t media; // Base media definition
100 | _hc_rulecol_t all_rules; // All rule sets in the stylesheet and document
101 | _hc_rulecol_t rules[HC_ELEMENT_MAX];
102 | // Rule sets organized by element
103 | };
104 |
105 |
106 | //
107 | // Functions...
108 | //
109 |
110 | extern void _hcCSSImportString(hc_css_t *css, hc_dict_t *props, const char *s);
111 | extern void _hcCSSSelAddStmt(hc_css_t *css, _hc_css_sel_t *sel, _hc_match_t match, const char *name, const char *value);
112 | extern void _hcCSSSelDelete(_hc_css_sel_t *sel);
113 | extern void _hcCSSSelHash(_hc_css_sel_t *sel, hc_sha3_256_t hash);
114 | extern _hc_css_sel_t *_hcCSSSelNew(hc_css_t *css, _hc_css_sel_t *prev, hc_element_t element, _hc_relation_t rel);
115 |
116 | extern void _hcRuleColAdd(hc_css_t *css, _hc_rulecol_t *col, _hc_rule_t *rule);
117 | extern void _hcRuleColClear(_hc_rulecol_t *col, int delete_rules);
118 | extern _hc_rule_t *_hcRuleColFindHash(_hc_rulecol_t *col, const hc_sha3_256_t hash);
119 | extern void _hcRuleDelete(_hc_rule_t *rule);
120 | extern _hc_rule_t *_hcRuleNew(hc_css_t *css, const hc_sha3_256_t hash, _hc_css_sel_t *sel, hc_dict_t *props);
121 |
122 |
123 | # ifdef __cplusplus
124 | }
125 | # endif // __cplusplus
126 | #endif // !HTMLCSS_CSS_PRIVATE_H
127 |
--------------------------------------------------------------------------------
/html-core.c:
--------------------------------------------------------------------------------
1 | //
2 | // HTML core functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "html-private.h"
13 | #include
14 |
15 |
16 | //
17 | // HTML element strings...
18 | //
19 |
20 | static const char * const elements[HC_ELEMENT_MAX] =
21 | {
22 | "", // "*"
23 | "!--",
24 | "!DOCTYPE",
25 | "a",
26 | "abbr",
27 | "acronym",
28 | "address",
29 | "applet",
30 | "area",
31 | "article",
32 | "aside",
33 | "audio",
34 | "b",
35 | "base",
36 | "basefont",
37 | "bdi",
38 | "bdo",
39 | "big",
40 | "blink",
41 | "blockquote",
42 | "body",
43 | "br",
44 | "button",
45 | "canvas",
46 | "caption",
47 | "center",
48 | "cite",
49 | "code",
50 | "col",
51 | "colgroup",
52 | "data",
53 | "datalist",
54 | "dd",
55 | "del",
56 | "details",
57 | "dfn",
58 | "dialog",
59 | "dir",
60 | "div",
61 | "dl",
62 | "dt",
63 | "em",
64 | "embed",
65 | "fieldset",
66 | "figcaption",
67 | "figure",
68 | "font",
69 | "footer",
70 | "form",
71 | "frame",
72 | "frameset",
73 | "h1",
74 | "h2",
75 | "h3",
76 | "h4",
77 | "h5",
78 | "h6",
79 | "head",
80 | "header",
81 | "hr",
82 | "html",
83 | "i",
84 | "iframe",
85 | "img",
86 | "input",
87 | "ins",
88 | "isindex",
89 | "kbd",
90 | "label",
91 | "legend",
92 | "li",
93 | "link",
94 | "main",
95 | "map",
96 | "mark",
97 | "menu",
98 | "meta",
99 | "meter",
100 | "multicol",
101 | "nav",
102 | "nobr",
103 | "noframes",
104 | "noscript",
105 | "object",
106 | "ol",
107 | "optgroup",
108 | "option",
109 | "output",
110 | "p",
111 | "param",
112 | "picture",
113 | "pre",
114 | "progress",
115 | "q",
116 | "rb",
117 | "rp",
118 | "rt",
119 | "rtc",
120 | "ruby",
121 | "s",
122 | "samp",
123 | "script",
124 | "section",
125 | "select",
126 | "small",
127 | "source",
128 | "spacer",
129 | "span",
130 | "strike",
131 | "strong",
132 | "style",
133 | "sub",
134 | "summary",
135 | "sup",
136 | "table",
137 | "tbody",
138 | "td",
139 | "template",
140 | "textarea",
141 | "tfoot",
142 | "th",
143 | "thead",
144 | "time",
145 | "title",
146 | "tr",
147 | "track",
148 | "tt",
149 | "u",
150 | "ul",
151 | "var",
152 | "video",
153 | "wbr"
154 | };
155 |
156 |
157 | //
158 | // Local functions...
159 | //
160 |
161 | static int compare_elements(const char **a, const char **b);
162 |
163 |
164 | //
165 | // 'hcElementString()' - Return the string associated with an element enum value.
166 | //
167 |
168 | const char * // O - HTML element string (lowercase)
169 | hcElementString(hc_element_t e) // I - HTML element enum
170 | {
171 | // Range check and return the appropriate string...
172 | if (e >= HC_ELEMENT_WILDCARD && e < HC_ELEMENT_MAX)
173 | return (elements[e]);
174 | else
175 | return ("(unknown)");
176 |
177 | }
178 |
179 |
180 | //
181 | // 'hcElementValue()' - Return the enum associated with an element string value.
182 | //
183 |
184 | hc_element_t // O - HTML element enum
185 | hcElementValue(const char *s) // I - HTML element string
186 | {
187 | const char **match; // Match from bsearch
188 |
189 |
190 | // Range check input...
191 | if (!s || !*s)
192 | return (HC_ELEMENT_UNKNOWN);
193 |
194 | // Search for the string...
195 | if ((match = (const char **)bsearch(&s, elements, sizeof(elements) / sizeof(elements[0]), sizeof(elements[0]), (int (*)(const void *, const void *))compare_elements)) != NULL)
196 | return ((hc_element_t)(match - elements));
197 | else
198 | return (HC_ELEMENT_UNKNOWN);
199 | }
200 |
201 |
202 | //
203 | // 'hcHTMLDelete()' - Free the memory used by a HTML document.
204 | //
205 |
206 | void
207 | hcHTMLDelete(hc_html_t *html) // I - HTML document
208 | {
209 | if (html)
210 | {
211 | hcNodeDelete(html, html->root);
212 | free(html);
213 | }
214 | }
215 |
216 |
217 | //
218 | // 'hcHTMLGetCSS()' - Get the stylesheet for a HTML document.
219 | //
220 |
221 | hc_css_t * // O - Stylesheet
222 | hcHTMLGetCSS(hc_html_t *html) // I - HTML document
223 | {
224 | return (html ? html->css : NULL);
225 | }
226 |
227 |
228 | //
229 | // 'hcNodeNew()' - Create a new HTML document.
230 | //
231 |
232 | hc_html_t * // O - HTML document
233 | hcHTMLNew(hc_pool_t *pool, // I - Memory pool
234 | hc_css_t *css) // I - Base stylesheet
235 | {
236 | hc_html_t *html; // New HTML document
237 |
238 |
239 | if ((html = (hc_html_t *)calloc(1, sizeof(hc_html_t))) != NULL)
240 | {
241 | html->pool = pool;
242 | html->css = css;
243 | html->error_cb = _hcDefaultErrorCB;
244 | html->url_cb = _hcDefaultURLCB;
245 | }
246 |
247 | return (html);
248 | }
249 |
250 |
251 | //
252 | // 'compare_elements()' - Compare two elements...
253 | //
254 |
255 | static int // O - Result of comparison
256 | compare_elements(const char **a, // I - First string
257 | const char **b) // I - Second string
258 | {
259 | #ifdef WIN32
260 | return (_stricmp(*a, *b));
261 | #else
262 | return (strcasecmp(*a, *b));
263 | #endif // WIN32
264 | }
265 |
--------------------------------------------------------------------------------
/install-sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # Install a program, script, or datafile.
4 | #
5 | # Copyright 2008-2012 by Apple Inc.
6 | #
7 | # This script is not compatible with BSD (or any other) install program, as it
8 | # allows owner and group changes to fail with a warning and makes sure that the
9 | # destination directory permissions are as specified - BSD install and the
10 | # original X11 install script did not change permissions of existing
11 | # directories. It also does not support the transform options since CUPS does
12 | # not use them...
13 | #
14 | # Original script from X11R5 (mit/util/scripts/install.sh)
15 | # Copyright 1991 by the Massachusetts Institute of Technology
16 | #
17 | # Permission to use, copy, modify, distribute, and sell this software and its
18 | # documentation for any purpose is hereby granted without fee, provided that
19 | # the above copyright notice appear in all copies and that both that
20 | # copyright notice and this permission notice appear in supporting
21 | # documentation, and that the name of M.I.T. not be used in advertising or
22 | # publicity pertaining to distribution of the software without specific,
23 | # written prior permission. M.I.T. makes no representations about the
24 | # suitability of this software for any purpose. It is provided "as is"
25 | # without express or implied warranty.
26 | #
27 | # Calling this script install-sh is preferred over install.sh, to prevent
28 | # `make' implicit rules from creating a file called install from it
29 | # when there is no Makefile.
30 |
31 | # set DOITPROG to echo to test this script
32 | # Don't use :- since 4.3BSD and earlier shells don't like it.
33 | doit="${DOITPROG-}"
34 |
35 | # Force umask to 022...
36 | umask 022
37 |
38 | # put in absolute paths if you don't have them in your path; or use env. vars.
39 | mvprog="${MVPROG-mv}"
40 | cpprog="${CPPROG-cp}"
41 | chmodprog="${CHMODPROG-chmod}"
42 | chownprog="${CHOWNPROG-chown}"
43 | chgrpprog="${CHGRPPROG-chgrp}"
44 | stripprog="${STRIPPROG-strip}"
45 | rmprog="${RMPROG-rm}"
46 | mkdirprog="${MKDIRPROG-mkdir}"
47 | gzipprog="${GZIPPROG-gzip}"
48 |
49 | transformbasename=""
50 | transform_arg=""
51 | instcmd="$mvprog"
52 | chmodcmd="$chmodprog 0755"
53 | chowncmd=""
54 | chgrpcmd=""
55 | stripcmd=""
56 | rmcmd="$rmprog -f"
57 | mvcmd="$mvprog"
58 | src=""
59 | dst=""
60 | dir_arg=""
61 |
62 | gzipcp() {
63 | # gzipcp from to
64 | $gzipprog -9 <"$1" >"$2"
65 | }
66 |
67 | while [ x"$1" != x ]; do
68 | case $1 in
69 | -c)
70 | instcmd="$cpprog"
71 | shift
72 | continue
73 | ;;
74 |
75 | -d)
76 | dir_arg=true
77 | shift
78 | continue
79 | ;;
80 |
81 | -m)
82 | chmodcmd="$chmodprog $2"
83 | shift
84 | shift
85 | continue
86 | ;;
87 |
88 | -o)
89 | chowncmd="$chownprog $2"
90 | shift
91 | shift
92 | continue
93 | ;;
94 |
95 | -g)
96 | chgrpcmd="$chgrpprog $2"
97 | shift
98 | shift
99 | continue
100 | ;;
101 |
102 | -s)
103 | stripcmd="$stripprog"
104 | shift
105 | continue
106 | ;;
107 |
108 | -z)
109 | instcmd="gzipcp"
110 | shift
111 | continue
112 | ;;
113 |
114 | *)
115 | if [ x"$src" = x ]; then
116 | src="$1"
117 | else
118 | dst="$1"
119 | fi
120 | shift
121 | continue
122 | ;;
123 | esac
124 | done
125 |
126 | if [ x"$src" = x ]; then
127 | echo "install-sh: No input file specified"
128 | exit 1
129 | fi
130 |
131 | if [ x"$dir_arg" != x ]; then
132 | dst="$src"
133 | src=""
134 |
135 | if [ -d "$dst" ]; then
136 | instcmd=:
137 | else
138 | instcmd=$mkdirprog
139 | fi
140 | else
141 | # Waiting for this to be detected by the "$instcmd $src $dsttmp" command
142 | # might cause directories to be created, which would be especially bad
143 | # if $src (and thus $dsttmp) contains '*'.
144 | if [ ! -f "$src" -a ! -d "$src" ]; then
145 | echo "install: $src does not exist"
146 | exit 1
147 | fi
148 |
149 | if [ x"$dst" = x ]; then
150 | echo "install: No destination specified"
151 | exit 1
152 | fi
153 |
154 | # If destination is a directory, append the input filename.
155 | if [ -d "$dst" ]; then
156 | dst="$dst/`basename $src`"
157 | fi
158 | fi
159 |
160 | ## this sed command emulates the dirname command
161 | dstdir="`echo $dst | sed -e 's,[^/]*$,,;s,/$,,;s,^$,.,'`"
162 |
163 | # Make sure that the destination directory exists.
164 | # This part is taken from Noah Friedman's mkinstalldirs script
165 |
166 | # Skip lots of stat calls in the usual case.
167 | if [ ! -d "$dstdir" ]; then
168 | defaultIFS='
169 | '
170 | IFS="${IFS-${defaultIFS}}"
171 |
172 | oIFS="${IFS}"
173 | # Some sh's can't handle IFS=/ for some reason.
174 | IFS='%'
175 | set - `echo ${dstdir} | sed -e 's@/@%@g' -e 's@^%@/@'`
176 | IFS="${oIFS}"
177 |
178 | pathcomp=''
179 |
180 | while [ $# -ne 0 ] ; do
181 | pathcomp="${pathcomp}${1}"
182 | shift
183 |
184 | if [ ! -d "${pathcomp}" ]; then $doit $mkdirprog "${pathcomp}"; fi
185 |
186 | pathcomp="${pathcomp}/"
187 | done
188 | fi
189 |
190 | if [ x"$dir_arg" != x ]; then
191 | # Make a directory...
192 | $doit $instcmd $dst || exit 1
193 |
194 | # Allow chown/chgrp to fail, but log a warning
195 | if [ x"$chowncmd" != x ]; then $doit $chowncmd $dst || echo "warning: Unable to change owner of $dst!"; fi
196 | if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd $dst || echo "warning: Unable to change group of $dst!"; fi
197 | if [ x"$chmodcmd" != x ]; then $doit $chmodcmd $dst || exit 1; fi
198 | else
199 | # Install a file...
200 | dstfile="`basename $dst`"
201 |
202 | # Check the destination file - for libraries just use the "-x" option
203 | # to strip...
204 | case "$dstfile" in
205 | *.a | *.dylib | *.sl | *.sl.* | *.so | *.so.*)
206 | stripopt="-x"
207 | ;;
208 | *)
209 | stripopt=""
210 | ;;
211 | esac
212 |
213 | # Make a temp file name in the proper directory.
214 | dsttmp="$dstdir/#inst.$$#"
215 |
216 | # Move or copy the file name to the temp name
217 | $doit $instcmd $src $dsttmp || exit 1
218 |
219 | # Update permissions and strip as needed, then move to the final name.
220 | # If the chmod, strip, rm, or mv commands fail, remove the installed
221 | # file...
222 | if [ x"$stripcmd" != x ]; then $doit $stripcmd $stripopt "$dsttmp" || echo "warning: Unable to strip $dst!"; fi
223 | if [ x"$chowncmd" != x ]; then $doit $chowncmd "$dsttmp" || echo "warning: Unable to change owner of $dst!"; fi
224 | if [ x"$chgrpcmd" != x ]; then $doit $chgrpcmd "$dsttmp" || echo "warning: Unable to change group of $dst!"; fi
225 |
226 | trap "rm -f ${dsttmp}" 0 &&
227 | if [ x"$chmodcmd" != x ]; then $doit $chmodcmd "$dsttmp"; fi &&
228 | $doit $rmcmd -f "$dstdir/$dstfile" &&
229 | $doit $mvcmd "$dsttmp" "$dstdir/$dstfile"
230 | fi
231 |
232 | exit 0
233 |
--------------------------------------------------------------------------------
/file.c:
--------------------------------------------------------------------------------
1 | //
2 | // File handling functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "file-private.h"
13 |
14 |
15 | //
16 | // 'hcFileDelete()' - Close a file and free any memory associated with it.
17 | //
18 |
19 | void
20 | hcFileDelete(hc_file_t *file) // I - File
21 | {
22 | if (!file)
23 | return;
24 |
25 | if (file->fp)
26 | gzclose(file->fp);
27 |
28 | free(file);
29 | }
30 |
31 |
32 | //
33 | // '_hcFileError()' - Report an error while reading the specified file.
34 | //
35 |
36 | bool // O - `true` to continue, `false` to stop
37 | _hcFileError(hc_file_t *file, // I - File
38 | const char *message, // I - Printf-style message
39 | ...) // I - Additional arguments as needed
40 | {
41 | bool ret; // Return value
42 | char saniurl[1024], // Sanitized URL
43 | *saniptr, // Pointer into sanitized URL
44 | temp[1024]; // Temporary message buffer
45 | const char *urlptr; // Pointer into URL
46 | va_list ap; // Pointer to arguments
47 |
48 |
49 | if (file->url)
50 | {
51 | // Sanitize the URL/filename...
52 | for (saniptr = saniurl, urlptr = file->url; *urlptr && saniptr < (saniurl + sizeof(saniurl) - 1); urlptr ++)
53 | {
54 | if (*urlptr < ' ' || *urlptr == '%')
55 | *saniptr++ = '_';
56 | else
57 | *saniptr++ = *urlptr;
58 | }
59 | *saniptr = '\0';
60 |
61 | // Create a new message format string with the correct prefix...
62 | if (file->linenum)
63 | snprintf(temp, sizeof(temp), "%s:%d: %s", saniurl, file->linenum, message);
64 | else if (file->url)
65 | snprintf(temp, sizeof(temp), "%s: %s", saniurl, message);
66 | message = temp;
67 | }
68 | else if (file->linenum)
69 | {
70 | // Create a new message format string with a line number prefix...
71 | snprintf(temp, sizeof(temp), "%d: %s", file->linenum, message);
72 | message = temp;
73 | }
74 |
75 | va_start(ap, message);
76 | ret = _hcPoolErrorv(file->pool, file->linenum, message, ap);
77 | va_end(ap);
78 |
79 | return (ret);
80 | }
81 |
82 |
83 | //
84 | // 'hcFileGetc()' - Get a character from a file.
85 | //
86 |
87 | int // O - Character or `EOF`
88 | hcFileGetc(hc_file_t *file) // I - File
89 | {
90 | int ch; // Current character
91 |
92 |
93 | if (file->bufptr)
94 | {
95 | if (file->bufptr < file->bufend)
96 | ch = (int)*(file->bufptr)++;
97 | else
98 | ch = EOF;
99 | }
100 | else
101 | ch = gzgetc(file->fp);
102 |
103 | if (ch == '\n')
104 | file->linenum ++;
105 |
106 | return (ch);
107 | }
108 |
109 |
110 | //
111 | // 'hcFileNewBuffer()' - Create a new file buffer stream.
112 | //
113 |
114 | hc_file_t * // O - File
115 | hcFileNewBuffer(hc_pool_t *pool, // I - Memory pool
116 | const void *buffer, // I - Buffer
117 | size_t bytes) // I - Size of buffer
118 | {
119 | hc_file_t *file; // File
120 |
121 |
122 | if ((file = calloc(1, sizeof(hc_file_t))) != NULL)
123 | {
124 | file->pool = pool;
125 | file->buffer = buffer;
126 | file->bufptr = file->buffer;
127 | file->bufend = file->buffer + bytes;
128 | file->linenum = 1;
129 | }
130 |
131 | return (file);
132 | }
133 |
134 |
135 | //
136 | // 'hcFileNewString()' - Create a new file string stream.
137 | //
138 |
139 | hc_file_t * // O - File
140 | hcFileNewString(hc_pool_t *pool, // I - Memory pool
141 | const char *s) // I - String
142 | {
143 | return (hcFileNewBuffer(pool, s, strlen(s)));
144 | }
145 |
146 |
147 | //
148 | // 'hcFileNewURL()' - Create a new file URL stream.
149 | //
150 |
151 | hc_file_t * // O - File
152 | hcFileNewURL(hc_pool_t *pool, // I - Memory pool
153 | const char *url, // I - URL or filename
154 | const char *baseurl) // I - Base URL or `NULL`
155 | {
156 | hc_file_t *file; // File
157 | const char *filename; // Local file
158 |
159 |
160 | _HC_DEBUG("hcFileNewURL(pool=%p, url=\"%s\", baseurl=\"%s\")\n", (void *)pool, url, baseurl);
161 |
162 | if ((filename = hcPoolGetURL(pool, url, baseurl)) == NULL)
163 | return (NULL);
164 |
165 | _HC_DEBUG("hcFileNewURL: filename=\"%s\"\n", filename);
166 |
167 | if ((file = calloc(1, sizeof(hc_file_t))) != NULL)
168 | {
169 | file->pool = pool;
170 | file->url = filename;
171 | file->fp = gzopen(filename, "rb");
172 | file->linenum = 1;
173 |
174 | if (!file->fp)
175 | {
176 | perror(filename);
177 | free(file);
178 | file = NULL;
179 | }
180 | }
181 |
182 | return (file);
183 | }
184 |
185 |
186 | //
187 | // 'hcFileRead()' - Read bytes from a file.
188 | //
189 |
190 | size_t // O - Number of bytes read
191 | hcFileRead(hc_file_t *file, // I - File
192 | void *buffer, // I - Buffer
193 | size_t bytes) // I - Number of bytes to read
194 | {
195 | ssize_t rbytes; // Number of bytes read
196 |
197 |
198 | if (!file || !buffer || bytes == 0)
199 | return (0);
200 |
201 | if (file->bufptr)
202 | {
203 | if ((size_t)(file->bufend - file->bufptr) < bytes)
204 | bytes = (size_t)(file->bufend - file->bufptr);
205 |
206 | if (bytes > 0)
207 | {
208 | memcpy(buffer, file->bufptr, bytes);
209 | file->bufptr += bytes;
210 | }
211 |
212 | return (bytes);
213 | }
214 | else if ((rbytes = gzread(file->fp, buffer, (unsigned)bytes)) < 0)
215 | return (0);
216 | else
217 | return ((size_t)rbytes);
218 | }
219 |
220 |
221 | //
222 | // 'hcFileSeek()' - Randomly access data within a file.
223 | //
224 |
225 | size_t // O - New file offset or 0 on error
226 | hcFileSeek(hc_file_t *file, // I - File
227 | size_t offset) // I - Offset within file
228 | {
229 | ssize_t soffset; // Seek offset
230 |
231 |
232 | if (!file)
233 | return (0);
234 |
235 | if (file->bufptr)
236 | {
237 | if (offset > (size_t)(file->bufend - file->buffer))
238 | offset = (size_t)(file->bufend - file->buffer);
239 |
240 | file->bufptr = file->buffer + offset;
241 |
242 | return (offset);
243 | }
244 |
245 | if ((soffset = gzseek(file->fp, (long)offset, SEEK_SET)) < 0)
246 | return (0);
247 | else
248 | return ((size_t)soffset);
249 | }
250 |
251 |
252 | //
253 | // 'hcFileUngetc()' - Return a character to a file.
254 | //
255 |
256 | void
257 | hcFileUngetc(hc_file_t *f, // I - File
258 | int ch) // I - Character
259 | {
260 | if (f->bufptr && f->bufptr > f->buffer)
261 | f->bufptr --;
262 | else if (f->fp)
263 | gzungetc(ch, f->fp);
264 |
265 | if (ch == '\n')
266 | f->linenum --;
267 | }
268 |
--------------------------------------------------------------------------------
/dict.c:
--------------------------------------------------------------------------------
1 | //
2 | // HTML attribute functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "common-private.h"
13 | #include "dict.h"
14 |
15 |
16 | //
17 | // Private types...
18 | //
19 |
20 | typedef struct _hc_pair_s // Key/value pair
21 | {
22 | const char *key; // Key
23 | const char *value; // Value
24 | } _hc_pair_t;
25 |
26 | struct _hc_dict_s // Dictionary
27 | {
28 | hc_pool_t *pool; // Memory pool
29 | size_t num_pairs; // Number of pairs
30 | size_t alloc_pairs; // Allocated pairs
31 | _hc_pair_t *pairs; // Key/value pairs
32 | };
33 |
34 |
35 | //
36 | // Local functions...
37 | //
38 |
39 | static int compare_pairs(_hc_pair_t *a, _hc_pair_t *b);
40 |
41 |
42 | //
43 | // 'hcDictCopy()' - Make a copy of a dictionary.
44 | //
45 |
46 | hc_dict_t * // O - New dictionary
47 | hcDictCopy(const hc_dict_t *dict) // I - Dictionary to copy
48 | {
49 | hc_dict_t *newdict; // New dictionary
50 |
51 |
52 | if (!dict)
53 | return (NULL);
54 |
55 | if ((newdict = calloc(1, sizeof(hc_dict_t))) == NULL)
56 | return (NULL);
57 |
58 | newdict->pool = dict->pool;
59 | newdict->num_pairs = dict->num_pairs;
60 | newdict->alloc_pairs = dict->num_pairs;
61 |
62 | if ((newdict->pairs = calloc(newdict->num_pairs, sizeof(_hc_pair_t))) == NULL)
63 | {
64 | free(newdict);
65 | return (NULL);
66 | }
67 |
68 | memcpy(newdict->pairs, dict->pairs, newdict->num_pairs * sizeof(_hc_pair_t));
69 |
70 | return (newdict);
71 | }
72 |
73 |
74 | //
75 | // 'hcDictDelete()' - Delete a dictionary.
76 | //
77 |
78 | void
79 | hcDictDelete(hc_dict_t *dict) // I - Dictionary
80 | {
81 | if (dict)
82 | {
83 | if (dict->pairs)
84 | free(dict->pairs);
85 |
86 | free(dict);
87 | }
88 | }
89 |
90 |
91 | //
92 | // 'hcDictGetCount()' - Return the number of key/value pairs in a dictionary.
93 | //
94 |
95 | size_t // O - Number of key/value pairs
96 | hcDictGetCount(const hc_dict_t *dict) // I - Dictionary
97 | {
98 | return (dict ? dict->num_pairs : 0);
99 | }
100 |
101 |
102 | //
103 | // 'hcDictGetIndexKeyValue()' - Return the key and value for the specified pair.
104 | //
105 |
106 | const char * // O - Value or `NULL` if `idx` is invalid.
107 | hcDictGetIndexKeyValue(
108 | const hc_dict_t *dict, // I - Dictionary
109 | size_t idx, // I - Index (0-based)
110 | const char **key) // O - Key or `NULL` if `idx` is invalid.
111 | {
112 | if (!dict || idx >= dict->num_pairs || !key)
113 | return (NULL);
114 |
115 | *key = dict->pairs[idx].key;
116 |
117 | return (dict->pairs[idx].value);
118 | }
119 |
120 |
121 | //
122 | // 'hcDictGetKeyValue()' - Get the value for a key in a dictionary.
123 | //
124 |
125 | const char * // O - Value or `NULL` if not found.
126 | hcDictGetKeyValue(
127 | const hc_dict_t *dict, // I - Dictionary
128 | const char *key) // I - Key string
129 | {
130 | _hc_pair_t temp, // Temporary search key
131 | *ptr; // Pointer to match
132 |
133 |
134 | if (!dict || dict->num_pairs == 0)
135 | return (NULL);
136 |
137 | temp.key = key;
138 | temp.value = NULL;
139 |
140 | if ((ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs)) != NULL)
141 | return (ptr->value);
142 | else
143 | return (NULL);
144 | }
145 |
146 |
147 | //
148 | // 'hcDictNew()' - Create a new dictionary.
149 | //
150 |
151 | hc_dict_t * // O - New dictionary
152 | hcDictNew(hc_pool_t *pool) // I - Memory pool
153 | {
154 | hc_dict_t *dict; // New dictionary
155 |
156 |
157 | if ((dict = (hc_dict_t *)calloc(1, sizeof(hc_dict_t))) != NULL)
158 | dict->pool = pool;
159 |
160 | return (dict);
161 | }
162 |
163 |
164 | //
165 | // 'hcDictRemoveKey()' - Remove a key/value pair from a dictionary.
166 | //
167 |
168 | void
169 | hcDictRemoveKey(hc_dict_t *dict, // I - Dictionary
170 | const char *key) // I - Key string
171 | {
172 | _hc_pair_t temp, // Temporary search key
173 | *ptr; // Pointer to match
174 | size_t idx; // Index into dictionary
175 |
176 |
177 | if (!dict || dict->num_pairs == 0)
178 | return;
179 |
180 | temp.key = key;
181 | temp.value = NULL;
182 |
183 | if ((ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs)) != NULL)
184 | {
185 | dict->num_pairs --;
186 |
187 | idx = (size_t)(ptr - dict->pairs);
188 |
189 | if (idx < dict->num_pairs)
190 | memmove(ptr, ptr + 1, (dict->num_pairs - idx) * sizeof(_hc_pair_t));
191 | }
192 | }
193 |
194 |
195 | //
196 | // 'hcDictSetKeyValue()' - Set a key/value pair in a dictionary.
197 | //
198 |
199 | void
200 | hcDictSetKeyValue(hc_dict_t *dict, // I - Dictionary
201 | const char *key, // I - Key string
202 | const char *value) // I - Value string
203 | {
204 | _hc_pair_t temp, // Search key
205 | *ptr = NULL; // New key/value pair
206 |
207 |
208 | _HC_DEBUG("hcDictSetKeyValue(dict=%p, key=\"%s\", value=\"%s\")\n", (void *)dict, key, value);
209 |
210 | if (!dict)
211 | {
212 | return;
213 | }
214 | else if (dict->num_pairs == 1 && !strcmp(dict->pairs[0].key, key))
215 | {
216 | ptr = dict->pairs;
217 | }
218 | else if (dict->num_pairs > 1)
219 | {
220 | temp.key = key;
221 |
222 | ptr = (_hc_pair_t *)bsearch(&temp, dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs);
223 | }
224 |
225 | if (ptr)
226 | {
227 | ptr->value = hcPoolGetString(dict->pool, value);
228 | return;
229 | }
230 |
231 | if (dict->num_pairs >= dict->alloc_pairs)
232 | {
233 | if ((ptr = realloc(dict->pairs, (dict->alloc_pairs + 4) * sizeof(_hc_pair_t))) == NULL)
234 | return;
235 |
236 | dict->alloc_pairs += 4;
237 | dict->pairs = ptr;
238 | }
239 |
240 | ptr = dict->pairs + dict->num_pairs;
241 | dict->num_pairs ++;
242 |
243 | ptr->key = hcPoolGetString(dict->pool, key);
244 | ptr->value = hcPoolGetString(dict->pool, value);
245 |
246 | qsort(dict->pairs, dict->num_pairs, sizeof(_hc_pair_t), (_hc_compare_func_t)compare_pairs);
247 |
248 | #ifdef DEBUG
249 | size_t i;
250 |
251 | _HC_DEBUG("hxDictSetKeyValue: num_pairs=%d\n", (int)dict->num_pairs);
252 | for (i = 0, ptr = dict->pairs; i < dict->num_pairs; i ++, ptr ++)
253 | _HC_DEBUG("hcDictSetKeyValue: pairs[%d].key=\"%s\", .value=\"%s\"\n", (int)i, ptr->key, ptr->value);
254 | #endif // DEBUG
255 | }
256 |
257 |
258 | //
259 | // 'compare_pairs()' - Compare two key/value pairs.
260 | //
261 |
262 | static int // O - Result of comparison
263 | compare_pairs(_hc_pair_t *a, // I - First pair
264 | _hc_pair_t *b) // I - Second pair
265 | {
266 | #ifdef _WIN32
267 | return (_stricmp(a->key, b->key));
268 | #else
269 | return (strcasecmp(a->key, b->key));
270 | #endif // _WIN32
271 | }
272 |
--------------------------------------------------------------------------------
/html.h:
--------------------------------------------------------------------------------
1 | //
2 | // HTML header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_HTML_H
13 | # define HTMLCSS_HTML_H
14 | # include "css.h"
15 | # ifdef __cplusplus
16 | extern "C" {
17 | # endif // __cplusplus
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef enum // HTML element enum
25 | {
26 | HC_ELEMENT_UNKNOWN = -2, // Unknown element/directive
27 | HC_ELEMENT_STRING = -1, // String
28 | HC_ELEMENT_WILDCARD, // Wildcard (*)
29 | HC_ELEMENT_COMMENT, // !--
30 | HC_ELEMENT_DOCTYPE, // !DOCTYPE
31 | HC_ELEMENT_A,
32 | HC_ELEMENT_ABBR,
33 | HC_ELEMENT_ACRONYM,
34 | HC_ELEMENT_ADDRESS,
35 | HC_ELEMENT_APPLET,
36 | HC_ELEMENT_AREA,
37 | HC_ELEMENT_ARTICLE,
38 | HC_ELEMENT_ASIDE,
39 | HC_ELEMENT_AUDIO,
40 | HC_ELEMENT_B,
41 | HC_ELEMENT_BASE,
42 | HC_ELEMENT_BASEFONT,
43 | HC_ELEMENT_BDI,
44 | HC_ELEMENT_BDO,
45 | HC_ELEMENT_BIG,
46 | HC_ELEMENT_BLINK,
47 | HC_ELEMENT_BLOCKQUOTE,
48 | HC_ELEMENT_BODY,
49 | HC_ELEMENT_BR,
50 | HC_ELEMENT_BUTTON,
51 | HC_ELEMENT_CANVAS,
52 | HC_ELEMENT_CAPTION,
53 | HC_ELEMENT_CENTER,
54 | HC_ELEMENT_CITE,
55 | HC_ELEMENT_CODE,
56 | HC_ELEMENT_COL,
57 | HC_ELEMENT_COLGROUP,
58 | HC_ELEMENT_DATA,
59 | HC_ELEMENT_DATALIST,
60 | HC_ELEMENT_DD,
61 | HC_ELEMENT_DEL,
62 | HC_ELEMENT_DETAILS,
63 | HC_ELEMENT_DFN,
64 | HC_ELEMENT_DIALOG,
65 | HC_ELEMENT_DIR,
66 | HC_ELEMENT_DIV,
67 | HC_ELEMENT_DL,
68 | HC_ELEMENT_DT,
69 | HC_ELEMENT_EM,
70 | HC_ELEMENT_EMBED,
71 | HC_ELEMENT_FIELDSET,
72 | HC_ELEMENT_FIGCAPTION,
73 | HC_ELEMENT_FIGURE,
74 | HC_ELEMENT_FONT,
75 | HC_ELEMENT_FOOTER,
76 | HC_ELEMENT_FORM,
77 | HC_ELEMENT_FRAME,
78 | HC_ELEMENT_FRAMESET,
79 | HC_ELEMENT_H1,
80 | HC_ELEMENT_H2,
81 | HC_ELEMENT_H3,
82 | HC_ELEMENT_H4,
83 | HC_ELEMENT_H5,
84 | HC_ELEMENT_H6,
85 | HC_ELEMENT_HEAD,
86 | HC_ELEMENT_HEADER,
87 | HC_ELEMENT_HR,
88 | HC_ELEMENT_HTML,
89 | HC_ELEMENT_I,
90 | HC_ELEMENT_IFRAME,
91 | HC_ELEMENT_IMG,
92 | HC_ELEMENT_INPUT,
93 | HC_ELEMENT_INS,
94 | HC_ELEMENT_ISINDEX,
95 | HC_ELEMENT_KBD,
96 | HC_ELEMENT_LABEL,
97 | HC_ELEMENT_LEGEND,
98 | HC_ELEMENT_LI,
99 | HC_ELEMENT_LINK,
100 | HC_ELEMENT_MAIN,
101 | HC_ELEMENT_MAP,
102 | HC_ELEMENT_MARK,
103 | HC_ELEMENT_MENU,
104 | HC_ELEMENT_META,
105 | HC_ELEMENT_METER,
106 | HC_ELEMENT_MULTICOL,
107 | HC_ELEMENT_NAV,
108 | HC_ELEMENT_NOBR,
109 | HC_ELEMENT_NOFRAMES,
110 | HC_ELEMENT_NOSCRIPT,
111 | HC_ELEMENT_OBJECT,
112 | HC_ELEMENT_OL,
113 | HC_ELEMENT_OPTGROUP,
114 | HC_ELEMENT_OPTION,
115 | HC_ELEMENT_OUTPUT,
116 | HC_ELEMENT_P,
117 | HC_ELEMENT_PARAM,
118 | HC_ELEMENT_PICTURE,
119 | HC_ELEMENT_PRE,
120 | HC_ELEMENT_PROGRESS,
121 | HC_ELEMENT_Q,
122 | HC_ELEMENT_RB,
123 | HC_ELEMENT_RP,
124 | HC_ELEMENT_RT,
125 | HC_ELEMENT_RTC,
126 | HC_ELEMENT_RUBY,
127 | HC_ELEMENT_S,
128 | HC_ELEMENT_SAMP,
129 | HC_ELEMENT_SCRIPT,
130 | HC_ELEMENT_SECTION,
131 | HC_ELEMENT_SELECT,
132 | HC_ELEMENT_SMALL,
133 | HC_ELEMENT_SOURCE,
134 | HC_ELEMENT_SPACER,
135 | HC_ELEMENT_SPAN,
136 | HC_ELEMENT_STRIKE,
137 | HC_ELEMENT_STRONG,
138 | HC_ELEMENT_STYLE,
139 | HC_ELEMENT_SUB,
140 | HC_ELEMENT_SUMMARY,
141 | HC_ELEMENT_SUP,
142 | HC_ELEMENT_TABLE,
143 | HC_ELEMENT_TBODY,
144 | HC_ELEMENT_TD,
145 | HC_ELEMENT_TEMPLATE,
146 | HC_ELEMENT_TEXTAREA,
147 | HC_ELEMENT_TFOOT,
148 | HC_ELEMENT_TH,
149 | HC_ELEMENT_THEAD,
150 | HC_ELEMENT_TIME,
151 | HC_ELEMENT_TITLE,
152 | HC_ELEMENT_TR,
153 | HC_ELEMENT_TRACK,
154 | HC_ELEMENT_TT,
155 | HC_ELEMENT_U,
156 | HC_ELEMENT_UL,
157 | HC_ELEMENT_VAR,
158 | HC_ELEMENT_VIDEO,
159 | HC_ELEMENT_WBR,
160 | HC_ELEMENT_MAX
161 | } hc_element_t;
162 |
163 | typedef struct _hc_node_s hc_node_t; // HTML node
164 |
165 | typedef struct _hc_html_s hc_html_t; // HTML document
166 |
167 |
168 | //
169 | // Functions...
170 | //
171 |
172 | extern const char *hcElementString(hc_element_t e) _HC_PUBLIC;
173 | extern hc_element_t hcElementValue(const char *s) _HC_PUBLIC;
174 |
175 | extern void hcHTMLDelete(hc_html_t *html) _HC_PUBLIC;
176 | extern hc_node_t *hcHTMLFindNode(hc_html_t *html, hc_node_t *current, hc_element_t element, const char *id) _HC_PUBLIC;
177 | extern hc_css_t *hcHTMLGetCSS(hc_html_t *html) _HC_PUBLIC;
178 | extern const char *hcHTMLGetDOCTYPE(hc_html_t *html) _HC_PUBLIC;
179 | extern hc_node_t *hcHTMLGetRootNode(hc_html_t *html) _HC_PUBLIC;
180 | extern bool hcHTMLImport(hc_html_t *html, hc_file_t *file) _HC_PUBLIC;
181 | extern hc_html_t *hcHTMLNew(hc_pool_t *pool, hc_css_t *css) _HC_PUBLIC;
182 | extern hc_node_t *hcHTMLNewRootNode(hc_html_t *html, const char *doctype) _HC_PUBLIC;
183 | extern void hcHTMLSetErrorCallback(hc_html_t *html, hc_error_cb_t cb, void *cbdata) _HC_PUBLIC;
184 | extern void hcHTMLSetURLCallback(hc_html_t *html, hc_url_cb_t cb, void *cbdata) _HC_PUBLIC;
185 |
186 | extern size_t hcNodeAttrGetCount(hc_node_t *node) _HC_PUBLIC;
187 | extern const char *hcNodeAttrGetIndexNameValue(hc_node_t *node, size_t idx, const char **name) _HC_PUBLIC;
188 | extern const char *hcNodeAttrGetNameValue(hc_node_t *node, const char *name) _HC_PUBLIC;
189 | extern void hcNodeAttrRemove(hc_node_t *node, const char *name) _HC_PUBLIC;
190 | extern void hcNodeAttrSetNameValue(hc_node_t *node, const char *name, const char *value) _HC_PUBLIC;
191 |
192 | extern bool hcNodeComputeCSSBox(hc_node_t *node, hc_compute_t compute, hc_box_t *box) _HC_PUBLIC;
193 | extern char *hcNodeComputeCSSContent(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC;
194 | extern hc_display_t hcNodeComputeCSSDisplay(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC;
195 | extern bool hcNodeComputeCSSMedia(hc_node_t *node, hc_compute_t compute, hc_media_t *media) _HC_PUBLIC;
196 | extern const hc_dict_t *hcNodeComputeCSSProperties(hc_node_t *node, hc_compute_t compute) _HC_PUBLIC;
197 | extern bool hcNodeComputeCSSTable(hc_node_t *node, hc_compute_t compute, hc_table_t *table) _HC_PUBLIC;
198 | extern bool hcNodeComputeCSSText(hc_node_t *node, hc_compute_t compute, hc_text_t *text) _HC_PUBLIC;
199 |
200 | extern void hcNodeDelete(hc_html_t *html, hc_node_t *node) _HC_PUBLIC;
201 | extern const char *hcNodeGetComment(hc_node_t *node) _HC_PUBLIC;
202 | extern hc_element_t hcNodeGetElement(hc_node_t *node) _HC_PUBLIC;
203 | extern hc_node_t *hcNodeGetFirstChildNode(hc_node_t *node) _HC_PUBLIC;
204 | extern hc_node_t *hcNodeGetLastChildNode(hc_node_t *node) _HC_PUBLIC;
205 | extern hc_node_t *hcNodeGetNextSiblingNode(hc_node_t *node) _HC_PUBLIC;
206 | extern hc_node_t *hcNodeGetParentNode(hc_node_t *node) _HC_PUBLIC;
207 | extern hc_node_t *hcNodeGetPrevSiblingNode(hc_node_t *node) _HC_PUBLIC;
208 | extern const char *hcNodeGetString(hc_node_t *node) _HC_PUBLIC;
209 | extern hc_node_t *hcNodeNewComment(hc_node_t *parent, const char *c) _HC_PUBLIC;
210 | extern hc_node_t *hcNodeNewElement(hc_node_t *parent, hc_element_t element) _HC_PUBLIC;
211 | extern hc_node_t *hcNodeNewString(hc_node_t *parent, const char *s) _HC_PUBLIC;
212 |
213 |
214 | # ifdef __cplusplus
215 | }
216 | # endif // __cplusplus
217 | #endif // !HTMLCSS_HTML_H
218 |
--------------------------------------------------------------------------------
/htmlcss.svg:
--------------------------------------------------------------------------------
1 |
2 |
129 |
--------------------------------------------------------------------------------
/css-rule.c:
--------------------------------------------------------------------------------
1 | //
2 | // CSS rule set support functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | # include "css-private.h"
13 | # include "pool-private.h"
14 |
15 |
16 | //
17 | // Local functions...
18 | //
19 |
20 | static int hc_compare_rules(_hc_rule_t **a, _hc_rule_t **b);
21 |
22 |
23 | //
24 | // '_hcCSSSelAddStmt()' - Add a matching statement to a selector.
25 | //
26 |
27 | void
28 | _hcCSSSelAddStmt(hc_css_t *css, // I - Stylesheet
29 | _hc_css_sel_t *sel, // I - Selector
30 | _hc_match_t match, // I - Match type
31 | const char *name, // I - Name, if any
32 | const char *value) // I - Value, if any
33 | {
34 | _hc_css_selstmt_t *temp; // Current statement
35 |
36 |
37 | if ((temp = realloc(sel->stmts, (sel->num_stmts + 1) * sizeof(_hc_css_selstmt_t))) != NULL)
38 | {
39 | sel->stmts = temp;
40 | temp += sel->num_stmts;
41 | sel->num_stmts ++;
42 |
43 | temp->match = match;
44 | temp->name = hcPoolGetString(css->pool, name);
45 | temp->value = hcPoolGetString(css->pool, value);
46 | }
47 | else
48 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector statement.");
49 | }
50 |
51 |
52 | //
53 | // '_hcCSSSelDelete()' - Free the memory used by a list of selectors.
54 | //
55 |
56 | void
57 | _hcCSSSelDelete(_hc_css_sel_t *sel) // I - Selectors
58 | {
59 | _hc_css_sel_t *prev; // Previous selector
60 |
61 |
62 | while (sel)
63 | {
64 | prev = sel->prev;
65 |
66 | if (sel->stmts)
67 | free(sel->stmts);
68 |
69 | free(sel);
70 |
71 | sel = prev;
72 | }
73 | }
74 |
75 |
76 | //
77 | // '_hcCSSSelHash()' - Create a SHA3-256 hash of a list of selectors.
78 | //
79 |
80 | void
81 | _hcCSSSelHash(_hc_css_sel_t *sel, // I - Selectors
82 | hc_sha3_256_t hash) // O - Hash of selectors
83 | {
84 | hc_sha3_t ctx; // SHA3 hashing context
85 |
86 |
87 | hcSHA3Init(&ctx);
88 |
89 | while (sel)
90 | {
91 | hcSHA3Update(&ctx, &sel->element, sizeof(sel->element));
92 | hcSHA3Update(&ctx, &sel->relation, sizeof(sel->relation));
93 | if (sel->stmts)
94 | hcSHA3Update(&ctx, sel->stmts, sel->num_stmts * sizeof(_hc_css_selstmt_t));
95 |
96 | sel = sel->prev;
97 | }
98 |
99 | hcSHA3Final(&ctx, hash, HC_SHA3_256_SIZE);
100 | }
101 |
102 |
103 | //
104 | // '_hcCSSSelNew()' - Create a new CSS selector.
105 | //
106 |
107 | _hc_css_sel_t * // O - New selector
108 | _hcCSSSelNew(hc_css_t *css, // I - Stylesheet
109 | _hc_css_sel_t *prev, // I - Previous selector, if any
110 | hc_element_t element, // I - Element or `HD_ELEMENT_WILDCARD`
111 | _hc_relation_t rel) // I - Relation to previous selector
112 | {
113 | _hc_css_sel_t *sel; // New selector
114 |
115 |
116 | if ((sel = (_hc_css_sel_t *)calloc(1, sizeof(_hc_css_sel_t))) != NULL)
117 | {
118 | sel->prev = prev;
119 | sel->element = element;
120 | sel->relation = rel;
121 | }
122 | else
123 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector.");
124 |
125 | return (sel);
126 | }
127 |
128 |
129 | //
130 | // '_hcRuleColAdd()' - Add a rule set to a collection.
131 | //
132 |
133 | void
134 | _hcRuleColAdd(hc_css_t *css, // I - Stylesheet
135 | _hc_rulecol_t *col, // I - Rule set collection
136 | _hc_rule_t *rule) // I - Rule set to add
137 | {
138 | _hc_rule_t **ptr; // New rule array pointer
139 |
140 |
141 | if (col->num_rules >= col->alloc_rules)
142 | {
143 | size_t alloc_rules; // New allocation
144 |
145 | if (col->alloc_rules == 0)
146 | alloc_rules = 1;
147 | else if (col->alloc_rules < 32)
148 | alloc_rules = col->alloc_rules * 2;
149 | else
150 | alloc_rules = col->alloc_rules + 32;
151 |
152 | if ((ptr = realloc(col->rules, alloc_rules * sizeof(_hc_rule_t *))) == NULL)
153 | {
154 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector rules.");
155 | return;
156 | }
157 |
158 | col->rules = ptr;
159 | col->alloc_rules = alloc_rules;
160 | }
161 |
162 | col->rules[col->num_rules] = rule;
163 | col->num_rules ++;
164 |
165 | if (col->num_rules > 1)
166 | col->needs_sort = 1;
167 | }
168 |
169 |
170 | //
171 | // '_hcRuleColClear()' - Empty a collection, optionally freeing the rule sets in it.
172 | //
173 |
174 | void
175 | _hcRuleColClear(
176 | _hc_rulecol_t *col, // I - Rule set collection
177 | int delete_rules) // I - 1 to delete rules, 0 to just clear collection
178 | {
179 | if (delete_rules && col->num_rules)
180 | {
181 | size_t i; // Looping var
182 | _hc_rule_t **ptr; // Pointer into array
183 |
184 | for (i = col->num_rules, ptr = col->rules; i > 0; i --, ptr ++)
185 | _hcRuleDelete(*ptr);
186 | }
187 |
188 | col->num_rules = 0;
189 | col->needs_sort = 0;
190 |
191 | if (col->alloc_rules > 0)
192 | {
193 | free(col->rules);
194 | col->alloc_rules = 0;
195 | col->rules = NULL;
196 | }
197 | }
198 |
199 |
200 | //
201 | // '_hcRuleColFindHash()' - Find a rule set using its hash.
202 | //
203 |
204 | _hc_rule_t * // O - Matching rule or `NULL`
205 | _hcRuleColFindHash(
206 | _hc_rulecol_t *col, // I - Rule set collection
207 | const hc_sha3_256_t hash) // I - SHA3-256 hash
208 | {
209 | _hc_rule_t key, // Search key
210 | *ptr, // Pointer to key
211 | **match; // Matching rule
212 |
213 |
214 | if (col->needs_sort)
215 | {
216 | qsort(col->rules, col->num_rules, sizeof(_hc_rule_t *), (_hc_compare_func_t)hc_compare_rules);
217 | col->needs_sort = 0;
218 | }
219 |
220 | memcpy(key.hash, hash, sizeof(key.hash));
221 | ptr = &key;
222 |
223 | if ((match = bsearch(&ptr, col->rules, col->num_rules, sizeof(_hc_rule_t *), (int (*)(const void *, const void *))hc_compare_rules)) != NULL)
224 | return (*match);
225 | else
226 | return (NULL);
227 | }
228 |
229 |
230 | //
231 | // '_hcRuleDelete()' - Free memory used by a rule set.
232 | //
233 |
234 | void
235 | _hcRuleDelete(_hc_rule_t *rule) // I - Rule set
236 | {
237 | _hcCSSSelDelete(rule->sel);
238 | hcDictDelete(rule->props);
239 | free(rule);
240 | }
241 |
242 |
243 | //
244 | // '_hcRuleNew()' - Create a new rule set.
245 | //
246 |
247 | _hc_rule_t * // I - Rule set
248 | _hcRuleNew(
249 | hc_css_t *css, // I - Stylesheet
250 | const hc_sha3_256_t hash, // I - SHA3-256 hash
251 | _hc_css_sel_t *sel, // I - Selectors
252 | hc_dict_t *props) // I - Properties dictionary
253 | {
254 | _hc_rule_t *rule; // New rule
255 |
256 |
257 | if ((rule = calloc(1, sizeof(_hc_rule_t))) != NULL)
258 | {
259 | memcpy(rule->hash, hash, sizeof(rule->hash));
260 | rule->sel = sel;
261 | rule->props = hcDictCopy(props);
262 | }
263 | else
264 | _hcPoolError(css->pool, 0, "Unable to allocate memory for selector rules.");
265 |
266 | return (rule);
267 | }
268 |
269 |
270 | //
271 | // 'hc_compare_rules()' - Compare two rules in a collection.
272 | //
273 |
274 | static int // O - Result of comparison
275 | hc_compare_rules(_hc_rule_t **a, // I - First rule
276 | _hc_rule_t **b) // I - Second rule
277 | {
278 | return (memcmp((*a)->hash, (*b)->hash, sizeof((*a)->hash)));
279 | }
280 |
--------------------------------------------------------------------------------
/Makefile.in:
--------------------------------------------------------------------------------
1 | #
2 | # Makefile for HTMLCSS library.
3 | #
4 | # https://github.com/michaelrsweet/htmlcss
5 | #
6 | # Copyright © 2018-2025 by Michael R Sweet.
7 | #
8 | # Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | # information.
10 | #
11 |
12 | # POSIX makefile
13 | .POSIX:
14 |
15 |
16 | # Build silently
17 | .SILENT:
18 |
19 |
20 | # Version numbers...
21 | HTMLCSS_VERSION = @HTMLCSS_VERSION@
22 | HTMLCSS_VERSION_MAJOR = @HTMLCSS_VERSION_MAJOR@
23 | HTMLCSS_VERSION_MINOR = @HTMLCSS_VERSION_MINOR@
24 |
25 |
26 | # Programs and options...
27 | AR = @AR@
28 | ARFLAGS = @ARFLAGS@
29 | CC = @CC@
30 | CFLAGS = @CFLAGS@ $(CPPFLAGS) $(OPTIM) $(WARNINGS)
31 | CODE_SIGN = @CODE_SIGN@
32 | CODESIGN_IDENTITY = -
33 | CPPFLAGS = @CPPFLAGS@ '-DVERSION="$(HTMLCSS_VERSION)"'
34 | CSFLAGS = -s "$(CODESIGN_IDENTITY)" @CSFLAGS@ --timestamp
35 | DSOFLAGS = @DSOFLAGS@ $(CFLAGS)
36 | INSTALL = @INSTALL@
37 | LDFLAGS = @LDFLAGS@ $(OPTIM)
38 | LIBS = @LIBS@ -lm
39 | LN = @LN@
40 | OPTIM = @OPTIM@
41 | RANLIB = @RANLIB@
42 | RM = @RM@ -f
43 | RMDIR = @RMDIR@
44 | SHELL = /bin/sh
45 | WARNINGS = @WARNINGS@
46 |
47 |
48 | # Targets
49 | LIBHTMLCSS = @LIBHTMLCSS@
50 | LIBHTMLCSS_STATIC = @LIBHTMLCSS_STATIC@
51 |
52 |
53 | # Directories...
54 | bindir = @bindir@
55 | datadir = @datadir@
56 | datarootdir = @datarootdir@
57 | exec_prefix = @exec_prefix@
58 | includedir = @includedir@
59 | infodir = @infodir@
60 | libdir = @libdir@
61 | libexecdir = @libexecdir@
62 | localstatedir = @localstatedir@
63 | mandir = @mandir@
64 | oldincludedir = @oldincludedir@
65 | prefix = @prefix@
66 | sbindir = @sbindir@
67 | sharedstatedir = @sharedstatedir@
68 | srcdir = @srcdir@
69 | sysconfdir = @sysconfdir@
70 | top_srcdir = @top_srcdir@
71 |
72 | BUILDROOT = $(DSTROOT)$(RPM_BUILD_ROOT)$(DESTDIR)
73 |
74 |
75 | # Build commands...
76 | .SUFFIXES: .c .h .o
77 | .c.o:
78 | echo Compiling $<...
79 | $(CC) $(CFLAGS) -c -o $@ $<
80 |
81 |
82 | # Files...
83 | PUBHEADERS = \
84 | common.h \
85 | css.h \
86 | dict.h \
87 | file.h \
88 | font.h \
89 | html.h \
90 | htmlcss.h \
91 | image.h \
92 | pool.h \
93 | run.h \
94 | sha3.h
95 |
96 | PRIVHEADERS = \
97 | common-private.h \
98 | css-private.h \
99 | file-private.h \
100 | font-private.h \
101 | html-private.h \
102 | pool-private.h
103 |
104 | LIBOBJS = \
105 | common.o \
106 | css-compute.o \
107 | css-core.o \
108 | css-import.o \
109 | css-rule.o \
110 | dict.o \
111 | file.o \
112 | font-core.o \
113 | font-extents.o \
114 | font-find.o \
115 | html-attr.o \
116 | html-core.o \
117 | html-find.o \
118 | html-load.o \
119 | html-node.o \
120 | image.o \
121 | pool.o \
122 | sha3.o
123 |
124 | OBJS = testhtmlcss.o $(LIBOBJS)
125 |
126 | TARGETS = \
127 | $(LIBHTMLCSS) \
128 | $(LIBHTMLCSS_STATIC) \
129 | testhtmlcss
130 |
131 | DOCFILES = \
132 | htmlcss.html \
133 | htmlcss.epub \
134 | htmlcss-512.png
135 | DOCFLAGS = \
136 | --author "Michael R Sweet" \
137 | --copyright "Copyright (c) 2018-2025 by Michael R Sweet" \
138 | --docversion $(VERSION) \
139 | --title "HTMLCSS Programming Manual"
140 | DOCSOURCES = \
141 | $(PUBHEADERS) \
142 | $(LIBOBJS:.o=.c)
143 |
144 |
145 | # Make everything
146 | all: $(TARGETS)
147 |
148 |
149 | # Clean everything
150 | clean:
151 | rm -f $(TARGETS) $(OBJS)
152 |
153 |
154 | # Install everything
155 | install: $(TARGETS)
156 | echo Installing header files to $(BUILDROOT)$(includedir)...
157 | $(INSTALL) -d -m 755 $(BUILDROOT)$(includedir)
158 | for file in $(PUBHEADERS); do \
159 | $(INSTALL) -c -m 644 $$file $(BUILDROOT)$(includedir); \
160 | done
161 | echo Installing library files to $(BUILDROOT)$(libdir)...
162 | $(INSTALL) -d -m 755 $(BUILDROOT)$(libdir)
163 | if test "x$(LIBHTMLCSS_STATIC)" != x; then \
164 | $(INSTALL) -c -m 644 $(LIBHTMLCSS_STATIC) $(BUILDROOT)$(libdir); \
165 | $(RANLIB) $(BUILDROOT)$(libdir)/$(LIBHTMLCSS_STATIC); \
166 | fi
167 | if test "x$(LIBHTMLCSS)" = xlibhtmlcss.so.1; then \
168 | $(INSTALL) -c -m 755 libhtmlcss.so.1 $(BUILDROOT)$(libdir); \
169 | ln -sf libhtmlcss.so.1 $(BUILDROOT)$(libdir)/libhtmlcss.so; \
170 | elif test "x$(LIBHTMLCSS)" = xlibhtmlcss.1.dylib; then \
171 | $(INSTALL) -c -m 755 libhtmlcss.1.dylib $(BUILDROOT)$(libdir); \
172 | codesign -s "$(CODESIGN_IDENTITY)" -o runtime --timestamp $(BUILDROOT)$(libdir)/libhtmlcss.1.dylib; \
173 | ln -sf libhtmlcss.1.dylib $(BUILDROOT)$(libdir)/libhtmlcss.dylib; \
174 | else \
175 | $(INSTALL) -c -m 644 $(LIBHTMLCSS) $(BUILDROOT)$(libdir); \
176 | $(RANLIB) $(BUILDROOT)$(libdir)/$(LIBHTMLCSS); \
177 | fi
178 | echo Installing pkg-config files to $(BUILDROOT)$(libdir)/pkgconfig...
179 | $(INSTALL) -d -m 755 $(BUILDROOT)$(libdir)/pkgconfig
180 | $(INSTALL) -c -m 644 htmlcss.pc $(BUILDROOT)$(libdir)/pkgconfig
181 | echo Installing documentation to $(BUILDROOT)$(datadir)/doc/htmlcss...
182 | $(INSTALL) -d -m 755 $(BUILDROOT)$(datadir)/doc/htmlcss
183 | for file in $(DOCFILES); do \
184 | $(INSTALL) -c -m 644 $$file $(BUILDROOT)$(datadir)/doc/htmlcss; \
185 | done
186 | echo Installing man page to $(BUILDROOT)$(mandir)/man3...
187 | $(INSTALL) -d -m 755 $(BUILDROOT)$(mandir)/man3
188 | $(INSTALL) -c -m 644 doc/htmlcss.3 $(BUILDROOT)$(mandir)/man3
189 |
190 |
191 | # Make documentation
192 | doc:
193 | codedoc $(DOCFLAGS) $(DOCSOURCES) >htmlcss.html
194 |
195 |
196 | # Run unit tests
197 | test: testhtmlcss
198 | ./testhtmlcss --all --css --font --html testhtmlcss.html >testhtmlcss.log 2>&1 || (cat testhtmlcss.log; exit 1)
199 |
200 |
201 | test-fonts: testhtmlcss
202 | ./testhtmlcss --font testsuite/Source/*.ttf testsuite/Source/*.otf
203 |
204 |
205 | # Unit test program
206 | testhtmlcss: testhtmlcss.o libhtmlcss.a
207 | $(CC) $(LDFLAGS) -o testhtmlcss testhtmlcss.o libhtmlcss.a $(LIBS)
208 |
209 |
210 | # Library
211 | libhtmlcss.a: $(LIBOBJS)
212 | echo Archiving $@...
213 | $(RM) $@
214 | $(AR) $(ARFLAGS) $@ $(LIBOBJS)
215 | $(RANLIB) $@
216 |
217 | libhtmlcss.so.1: $(LIBOBJS)
218 | echo Linking $@...
219 | $(CC) $(DSOFLAGS) -shared -o $@ -Wl,-soname,$@ $(LIBOBJS) $(LIBS)
220 |
221 | libhtmlcss.1.dylib: $(LIBOBJS)
222 | echo Linking $@...
223 | $(CC) $(DSOFLAGS) -dynamiclib -o $@ -install_name $(libdir)/$@ -current_version $(PDFIO_VERSION_MAJOR).$(PDFIO_VERSION_MINOR) -compatibility_version 1.0 $(LIBOBJS) $(LIBS)
224 |
225 |
226 | # htmlcss1.def (Windows DLL exports file...)
227 | #
228 | # I'd love to use __declspec(dllexport) but MS puts it before the function
229 | # declaration instead of after like everyone else, and it breaks Codedoc and
230 | # other tools I rely on...
231 | htmlcss1.def: $(LIBOBJS) Makefile
232 | echo Generating $@...
233 | echo "LIBRARY htmlcss1" >$@
234 | echo "VERSION $(HTMLCSS_VERSION_MAJOR).$(HTMLCSS_VERSION_MINOR)" >>$@
235 | echo "EXPORTS" >>$@
236 | nm $(LIBOBJS) 2>/dev/null | grep "T _" | awk '{print $$3}' | \
237 | grep -v '^_ttf' | sed -e '1,$$s/^_//' | sort >>$@
238 |
239 |
240 | # Dependencies
241 | css-import.o: default-css.h
242 |
243 | default-css.h: default.css Makefile
244 | echo "static const char *default_css =" >default-css.h
245 | sed -e '1,$$s/\\/\\\\/g' -e '1,$$s/"/\\"/g' >default-css.h
246 | echo ";" >>default-css.h
247 |
248 | $(OBJS): Makefile $(PUBHEADERS) $(PRIVHEADERS)
249 |
250 |
251 | # Scan code with the Clang static analyzer
252 | clang:
253 | clang $(CPPFLAGS) -DDEBUG -Werror --analyze $(LIBOBJS:.o=.c)
254 | rm -rf $(LIBOBJS:.o=.plist)
255 |
256 |
257 | # Scan the code using Cppcheck
258 | cppcheck:
259 | cppcheck --template=gcc --suppress=cert-API01-C --suppress=cert-EXP05-C --suppress=cert-INT31-c --suppress=cert-MSC24-C --suppress=cert-STR05-C $(CPPFLAGS) $(LIBOBJS:.o=.c) 2>cppcheck.log
260 | @test -s cppcheck.log && (echo ""; echo "Errors detected:"; echo ""; cat cppcheck.log; exit 1) || exit 0
261 |
--------------------------------------------------------------------------------
/image.c:
--------------------------------------------------------------------------------
1 | //
2 | // Image handling functions for HTMLCSS library.
3 | //
4 | // Note: The purpose of these functions is currently just to discover the
5 | // dimensions and format of an image file and not to decode or transform the
6 | // contents of an image.
7 | //
8 | // https://github.com/michaelrsweet/htmlcss
9 | //
10 | // Copyright © 2019-2025 by Michael R Sweet.
11 | //
12 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
13 | // information.
14 | //
15 |
16 | #include "image.h"
17 | #include "common-private.h"
18 |
19 |
20 | //
21 | // Types...
22 | //
23 |
24 | typedef enum _hc_res_e
25 | {
26 | _HC_RES_NONE, // No units
27 | _HC_RES_PER_INCH, // Pixels per inch
28 | _HC_RES_PER_CM // Pixels per cm
29 | } _hc_res_t;
30 |
31 | struct _hc_image_s
32 | {
33 | hc_pool_t *pool; // Memory pool
34 | const char *format; // MIME media type
35 | int width, // Width in pixels
36 | height; // Height in pixels
37 | int xres, // Width resolution
38 | yres; // Height resolution
39 | _hc_res_t units; // Resolution units
40 | };
41 |
42 |
43 | //
44 | // Local functions...
45 | //
46 |
47 |
48 | //
49 | // 'hcImageDelete()' - Delete an image object.
50 | //
51 |
52 | void
53 | hcImageDelete(hc_image_t *image) // I - Image object
54 | {
55 | free(image);
56 | }
57 |
58 |
59 | //
60 | // 'hcImageGetFormat()' - Get the MIME media type for the image.
61 | //
62 |
63 | const char * // O - MIME media type
64 | hcImageGetFormat(hc_image_t *image) // I - Image object
65 | {
66 | return (image ? image->format : NULL);
67 | }
68 |
69 |
70 | //
71 | // 'hcImageGetHeight()' - Get the height of an image.
72 | //
73 |
74 | int // O - Height in pixels
75 | hcImageGetHeight(hc_image_t *image) // I - Image object
76 | {
77 | return (image ? image->height : 0);
78 | }
79 |
80 |
81 | //
82 | // 'hcImageGetSize()' - Get the natural size of an image.
83 | //
84 |
85 | hc_size_t // O - CSS dimensions
86 | hcImageGetSize(hc_image_t *image) // I - Image object
87 | {
88 | hc_size_t size; // CSS dimensions
89 |
90 |
91 | if (image)
92 | {
93 | if (image->xres > 0 && image->yres > 0 && image->units != _HC_RES_NONE)
94 | {
95 | if (image->units == _HC_RES_PER_INCH)
96 | {
97 | /*
98 | * Convert from PPI to points...
99 | */
100 |
101 | size.width = 72.0f * image->width / image->xres;
102 | size.height = 72.0f * image->height / image->yres;
103 | }
104 | else
105 | {
106 | /*
107 | * Convert from PPCM to points...
108 | */
109 |
110 | size.width = 72.0f / 2.54f * image->width / image->xres;
111 | size.height = 72.0f / 2.54f * image->height / image->yres;
112 | }
113 | }
114 | else
115 | {
116 | /*
117 | * Default resolution is 100 PPI, CSS units are points (72 points per inch).
118 | */
119 |
120 | size.width = 0.72f * image->width;
121 | size.height = 0.72f * image->height;
122 | }
123 | }
124 | else
125 | {
126 | size.width = 0.0f;
127 | size.height = 0.0f;
128 | }
129 |
130 | return (size);
131 | }
132 |
133 |
134 | //
135 | // 'hcImageGetWidth()' - Get the width of an image.
136 | //
137 |
138 | int // O - Width in pixels
139 | hcImageGetWidth(hc_image_t *image) // I - Image object
140 | {
141 | return (image ? image->width : 0);
142 | }
143 |
144 |
145 | //
146 | // 'hcImageNew()' - Create a new image object.
147 | //
148 |
149 | hc_image_t * // O - Image object
150 | hcImageNew(hc_pool_t *pool, // I - Memory pool
151 | hc_file_t *file) // I - File
152 | {
153 | hc_image_t *image; // Image object
154 | unsigned char buffer[2048]; // Buffer
155 | size_t bytes; // Bytes in buffer
156 |
157 |
158 | _HC_DEBUG("hcImageNew(pool=%p, file=%p)\n", (void *)pool, (void *)file);
159 |
160 | if (!pool || !file)
161 | return (NULL);
162 |
163 | if ((image = (hc_image_t *)calloc(1, sizeof(hc_image_t))) == NULL)
164 | return (NULL);
165 |
166 | image->pool = pool;
167 |
168 | bytes = hcFileRead(file, buffer, sizeof(buffer));
169 |
170 | _HC_DEBUG("hcImageNew: Read %d bytes from file.\n", (int)bytes);
171 | _HC_DEBUG("hcImageNew: \\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\\%03o\n", buffer[0], buffer[1], buffer[2], buffer[3], buffer[4], buffer[5], buffer[6], buffer[7], buffer[8], buffer[9], buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], buffer[15]);
172 |
173 | if (bytes > 27 && !memcmp(buffer, "\211PNG\015\012\032\012\000\000\000\015IHDR", 16))
174 | {
175 | /*
176 | * PNG image...
177 | */
178 |
179 | image->format = "image/png";
180 | image->width = (buffer[16] << 24) | (buffer[17] << 16) | (buffer[18] << 8) | buffer[19];
181 | image->height = (buffer[20] << 24) | (buffer[21] << 16) | (buffer[22] << 8) | buffer[23];
182 | }
183 | else if (bytes > 12 && (!memcmp(buffer, "GIF87a", 6) || !memcmp(buffer, "GIF89a", 6)))
184 | {
185 | /*
186 | * GIF image...
187 | */
188 |
189 | image->format = "image/gif";
190 | image->width = (buffer[7] << 8) | buffer[6];
191 | image->height = (buffer[9] << 8) | buffer[8];
192 | }
193 | else if (bytes > 3 && !memcmp(buffer, "\377\330\377", 3))
194 | {
195 | /*
196 | * JPEG image...
197 | */
198 |
199 | unsigned char *bufptr = buffer + 2,
200 | // Pointer into buffer
201 | *bufend = buffer + bytes;
202 | // End of buffer
203 | size_t length; // Length of marker
204 |
205 | image->format = "image/jpeg";
206 |
207 | /*
208 | * Scan the file for a SOFn marker, then we can get the dimensions...
209 | */
210 |
211 | while (bufptr < bufend)
212 | {
213 | if (*bufptr == 0xff)
214 | {
215 | bufptr ++;
216 |
217 | if (bufptr >= bufend)
218 | {
219 | /*
220 | * If we are at the end of the current buffer, re-fill and continue...
221 | */
222 |
223 | if ((bytes = hcFileRead(file, buffer, sizeof(buffer))) == 0)
224 | break;
225 |
226 | bufptr = buffer;
227 | bufend = buffer + bytes;
228 | }
229 |
230 | if (*bufptr == 0xff)
231 | continue;
232 |
233 | if ((bufptr + 16) >= bufend)
234 | {
235 | /*
236 | * Read more of the marker...
237 | */
238 |
239 | bytes = (size_t)(bufend - bufptr);
240 |
241 | memmove(buffer, bufptr, bytes);
242 | bufptr = buffer;
243 | bufend = buffer + bytes;
244 |
245 | if ((bytes = hcFileRead(file, bufend, sizeof(buffer) - bytes)) == 0)
246 | break;
247 |
248 | bufend += bytes;
249 | }
250 |
251 | length = (size_t)((bufptr[1] << 8) | bufptr[2]);
252 |
253 | _HC_DEBUG("hcImageNew: JPEG X'FF%02X' (length %u)\n", *bufptr, (unsigned)length);
254 |
255 | if (*bufptr == 0xe0 && length >= 16 && !memcmp(bufptr + 3, "JFIF", 5))
256 | {
257 | /*
258 | * APP0 marker for JFIF...
259 | */
260 |
261 | if (bufptr[10] == 1)
262 | {
263 | image->units = _HC_RES_PER_INCH;
264 | image->xres = (bufptr[11] << 8) | bufptr[12];
265 | image->yres = (bufptr[13] << 8) | bufptr[14];
266 | }
267 | else if (bufptr[10] == 2)
268 | {
269 | image->units = _HC_RES_PER_CM;
270 | image->xres = (bufptr[11] << 8) | bufptr[12];
271 | image->yres = (bufptr[13] << 8) | bufptr[14];
272 | }
273 | }
274 | else if ((*bufptr >= 0xc0 && *bufptr <= 0xc3) ||
275 | (*bufptr >= 0xc5 && *bufptr <= 0xc7) ||
276 | (*bufptr >= 0xc9 && *bufptr <= 0xcb) ||
277 | (*bufptr >= 0xcd && *bufptr <= 0xcf))
278 | {
279 | /*
280 | * SOFn marker, look for dimensions...
281 | */
282 |
283 | image->width = (bufptr[6] << 8) | bufptr[7];
284 | image->height = (bufptr[4] << 8) | bufptr[5];
285 | break;
286 | }
287 |
288 | /*
289 | * Skip past this marker...
290 | */
291 |
292 | bufptr ++;
293 | bytes = (size_t)(bufend - bufptr);
294 |
295 | while (length >= bytes)
296 | {
297 | length -= bytes;
298 |
299 | if ((bytes = hcFileRead(file, buffer, sizeof(buffer))) == 0)
300 | break;
301 |
302 | bufptr = buffer;
303 | bufend = buffer + bytes;
304 | }
305 |
306 | if (length > bytes)
307 | break;
308 |
309 | bufptr += length;
310 | }
311 | }
312 |
313 | if (image->width == 0 || image->height == 0)
314 | {
315 | free(image);
316 | return (NULL);
317 | }
318 | }
319 | else
320 | {
321 | free(image);
322 | return (NULL);
323 | }
324 |
325 | return (image);
326 | }
327 |
--------------------------------------------------------------------------------
/pool.c:
--------------------------------------------------------------------------------
1 | //
2 | // Memory pool functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "pool-private.h"
13 |
14 |
15 | //
16 | // Local functions...
17 | //
18 |
19 | static int compare_strings(char **a, char **b);
20 |
21 |
22 | //
23 | // 'hcPoolDelete()' - Free the memory used by a pool.
24 | //
25 |
26 | void
27 | hcPoolDelete(hc_pool_t *pool) // I - Memory pool
28 | {
29 | if (pool)
30 | {
31 | if (pool->num_fonts > 0)
32 | _hcPoolDeleteFonts(pool);
33 |
34 | if (pool->num_strings > 0)
35 | {
36 | size_t i; // Looping var
37 | char **temp; // String pointer
38 |
39 | for (i = pool->num_strings, temp = pool->strings; i > 0; i --, temp ++)
40 | free(*temp);
41 |
42 | free(pool->strings);
43 | }
44 |
45 | free(pool->last_error);
46 | free(pool);
47 | }
48 | }
49 |
50 |
51 | //
52 | // '_hcPoolError()' - Display an error message.
53 | //
54 |
55 | bool // O - `true` to continue, `false` to stop
56 | _hcPoolError(
57 | hc_pool_t *pool, // I - Memory pool
58 | int linenum, // I - Line number in file or 0
59 | const char *message, // I - Printf-style message string
60 | ...) // I - Additional arguments as needed
61 | {
62 | bool ret; // Return value
63 | va_list ap; // Pointer to additional arguments
64 |
65 |
66 | va_start(ap, message);
67 | ret = _hcPoolErrorv(pool, linenum, message, ap);
68 | va_end(ap);
69 |
70 | return (ret);
71 | }
72 |
73 |
74 | //
75 | // '_hcPoolErrorv()' - Display an error message.
76 | //
77 |
78 | bool // O - `true` to continue, `false` to stop
79 | _hcPoolErrorv(
80 | hc_pool_t *pool, // I - Memory pool
81 | int linenum, // I - Line number in file or 0
82 | const char *message, // I - Printf-style message string
83 | va_list ap) // I - Pointer to additional arguments
84 | {
85 | char buffer[8192]; // Message buffer
86 |
87 |
88 | vsnprintf(buffer, sizeof(buffer), message, ap);
89 |
90 | free(pool->last_error);
91 | pool->last_error = strdup(buffer);
92 |
93 | return ((pool->error_cb)(pool->error_ctx, buffer, linenum));
94 | }
95 |
96 |
97 | //
98 | // 'hcPoolGetLastError()' - Return the last error message recorded.
99 | //
100 |
101 | const char * // O - Last error message or `NULL`
102 | hcPoolGetLastError(hc_pool_t *pool) // I - Memory pool
103 | {
104 | return (pool ? pool->last_error : NULL);
105 | }
106 |
107 |
108 | //
109 | // 'hcPoolGetString()' - Find or copy a string.
110 | //
111 | // This function finds or makes a copy of the passed string that will be freed
112 | // when the corresponding memory pool is deleted. Since the memory pool only
113 | // maintains a single copy of any string, copied strings are immutable.
114 | //
115 |
116 | const char * // O - New string pointer
117 | hcPoolGetString(
118 | hc_pool_t *pool, // I - Memory pool
119 | const char *s) // I - String to find/copy
120 | {
121 | char *news, // New string
122 | **temp; // Temporary string pointer
123 |
124 |
125 | if (!pool || !s)
126 | return (NULL);
127 | else if (!*s)
128 | return ("");
129 |
130 | if (pool->num_strings == 1 && !strcmp(pool->strings[0], s))
131 | {
132 | _HC_DEBUG("hcPoolGetString: Existing string '%s' (%p) found.\n", pool->strings[0], (void *)pool->strings[0]);
133 | return (pool->strings[0]);
134 | }
135 | else if (pool->num_strings > 1)
136 | {
137 | if ((temp = bsearch(&s, pool->strings, pool->num_strings, sizeof(char *), (_hc_compare_func_t)compare_strings)) != NULL)
138 | {
139 | _HC_DEBUG("hcPoolGetString: Existing string '%s' (%p) found.\n", *temp, (void *)*temp);
140 | return (*temp);
141 | }
142 | }
143 |
144 | if (pool->num_strings >= pool->alloc_strings)
145 | {
146 | if ((temp = realloc(pool->strings, (pool->alloc_strings + 32) * sizeof(char *))) == NULL)
147 | return (NULL);
148 |
149 | pool->alloc_strings += 32;
150 | pool->strings = temp;
151 | }
152 |
153 | temp = pool->strings + pool->num_strings;
154 | *temp = news = strdup(s);
155 | pool->num_strings ++;
156 |
157 | if (pool->num_strings > 1)
158 | qsort(pool->strings, pool->num_strings, sizeof(char *), (_hc_compare_func_t)compare_strings);
159 |
160 | _HC_DEBUG("hcPoolGetString: New string '%s' (%p), pool now contains %d strings.\n", news, (void *)news, (int)pool->num_strings);
161 |
162 | return (news);
163 | }
164 |
165 |
166 | //
167 | // 'hcPoolGetURL()' - Get a file corresponding to a URL.
168 | //
169 |
170 | const char * // O - Filename or `NULL` on error
171 | hcPoolGetURL(hc_pool_t *pool, // I - Memory pool
172 | const char *url, // I - URL
173 | const char *baseurl) // I - Base URL, if any
174 | {
175 | const char *mapped; // Mapped file
176 | char *ptr, // Pointer into URL
177 | temp[1024], // Temporary path
178 | newurl[1024]; // New URL
179 |
180 |
181 | if (*url == '/')
182 | {
183 | if (!baseurl)
184 | return (hcPoolGetString(pool, url));
185 | else if (!strncmp(baseurl, "http://", 7))
186 | {
187 | strncpy(temp, baseurl, sizeof(temp) - 1);
188 | temp[sizeof(temp) - 1] = '\0';
189 | if ((ptr = strchr(temp + 7, '/')) != NULL)
190 | *ptr = '\0';
191 |
192 | snprintf(newurl, sizeof(newurl), "%s%s", temp, url);
193 | url = newurl;
194 | }
195 | else if (!strncmp(baseurl, "https://", 8))
196 | {
197 | strncpy(temp, baseurl, sizeof(temp) - 1);
198 | temp[sizeof(temp) - 1] = '\0';
199 | if ((ptr = strchr(temp + 8, '/')) != NULL)
200 | *ptr = '\0';
201 |
202 | snprintf(newurl, sizeof(newurl), "%s%s", temp, url);
203 | url = newurl;
204 | }
205 | else
206 | return (hcPoolGetString(pool, url));
207 | }
208 | else if (strncmp(url, "http://", 7) && strncmp(url, "https://", 8))
209 | {
210 | if (!baseurl)
211 | {
212 | getcwd(temp, sizeof(temp));
213 | snprintf(newurl, sizeof(newurl), "%s/%s", temp, url);
214 |
215 | return (hcPoolGetString(pool, newurl));
216 | }
217 | else
218 | {
219 | strncpy(temp, baseurl, sizeof(temp) - 1);
220 | temp[sizeof(temp) - 1] = '\0';
221 |
222 | if ((ptr = strrchr(temp, '/')) != NULL)
223 | *ptr = '\0';
224 |
225 | snprintf(newurl, sizeof(newurl), "%s/%s", temp, url);
226 |
227 | if (newurl[0] == '/')
228 | return (hcPoolGetString(pool, newurl));
229 |
230 | url = newurl;
231 | }
232 | }
233 |
234 | if ((mapped = (pool->url_cb)(pool->url_ctx, url, temp, sizeof(temp))) != NULL)
235 | {
236 | if (!pool->urls)
237 | pool->urls = hcDictNew(pool);
238 |
239 | hcDictSetKeyValue(pool->urls, url, temp);
240 | mapped = hcPoolGetString(pool, temp);
241 | }
242 |
243 | return (mapped);
244 | }
245 |
246 |
247 | //
248 | // 'hcPoolNew()' - Create a new memory pool.
249 | //
250 |
251 | hc_pool_t * // O - New memory pool
252 | hcPoolNew(void)
253 | {
254 | hc_pool_t *pool = (hc_pool_t *)calloc(1, sizeof(hc_pool_t));
255 | // New memory pool
256 |
257 | if (pool)
258 | {
259 | if ((pool->loc = localeconv()) != NULL)
260 | {
261 | if (!pool->loc->decimal_point || !strcmp(pool->loc->decimal_point, "."))
262 | pool->loc = NULL;
263 | else
264 | pool->loc_declen = strlen(pool->loc->decimal_point);
265 | }
266 |
267 | pool->error_cb = _hcDefaultErrorCB;
268 | pool->url_cb = _hcDefaultURLCB;
269 | }
270 |
271 | return (pool);
272 | }
273 |
274 |
275 | //
276 | // 'hcPoolSetErrorCallback()' - Set the error reporting callback.
277 | //
278 | // The default error callback writes the message to `stderr`.
279 | //
280 | // The error callback returns 1 to continue processing or 0 to stop immediately.
281 | //
282 |
283 | void
284 | hcPoolSetErrorCallback(
285 | hc_pool_t *pool, // I - Memory pool
286 | hc_error_cb_t cb, // I - Error callback or `NULL` for the default
287 | void *ctx) // I - Context pointer for callback
288 | {
289 | if (!pool)
290 | return;
291 |
292 | pool->error_cb = cb ? cb : _hcDefaultErrorCB;
293 | pool->error_ctx = ctx;
294 | }
295 |
296 |
297 | //
298 | // 'hcPoolSetURLCallback()' - Set the URL callback.
299 | //
300 | // The default URL callback supports local files (only).
301 | //
302 | // The URL callback returns a local pathname (copied to the specified buffer)
303 | // or `NULL` if the URL cannot be loaded/found.
304 | //
305 |
306 | void
307 | hcPoolSetURLCallback(
308 | hc_pool_t *pool, // I - Memory pool
309 | hc_url_cb_t cb, // I - URL callback or `NULL` for the default
310 | void *ctx) // I - Context pointer for callback
311 | {
312 | if (!pool)
313 | return;
314 |
315 | pool->url_cb = cb ? cb : _hcDefaultURLCB;
316 | pool->url_ctx = ctx;
317 | }
318 |
319 |
320 | //
321 | // 'compare_strings()' - Compare two strings...
322 | //
323 |
324 | static int // O - Result of comparison
325 | compare_strings(char **a, // I - First string
326 | char **b) // I - Second string
327 | {
328 | return (strcmp(*a, *b));
329 | }
330 |
--------------------------------------------------------------------------------
/sha3.c:
--------------------------------------------------------------------------------
1 | //
2 | // SHA3 hash implementation for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2015-2025 Michael R Sweet. All rights reserved.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 | // The following code is adapted from the
12 | // "Keccak-readable-and-compact.c" source code from the official
13 | // Keccak reference implementation code repository:
14 | //
15 | // https://github.com/gvanas/KeccakCodePackage.git
16 | //
17 | // The original code bore the following notice:
18 | //
19 | // Implementation by the Keccak, Keyak and Ketje Teams, namely,
20 | // Guido Bertoni, Joan Daemen, Michaël Peeters, Gilles Van Assche
21 | // and Ronny Van Keer, hereby denoted as "the implementer".
22 | //
23 | // For more information, feedback or questions, please refer to
24 | // our websites:
25 | //
26 | // http://keccak.noekeon.org/
27 | // http://keyak.noekeon.org/
28 | // http://ketje.noekeon.org/
29 | //
30 | // To the extent possible under law, the implementer has waived
31 | // all copyright and related or neighboring rights to the source
32 | // code in this file.
33 | //
34 | // http://creativecommons.org/publicdomain/zero/1.0/
35 | //
36 |
37 | #include "common-private.h"
38 | #include
39 | #include "sha3.h"
40 |
41 |
42 | //
43 | // Endian tests to get LITTLE_ENDIAN defined to 1 on little-endian
44 | // systems; force it with "-DLITTLE_ENDIAN=1" to get the "optimized"
45 | // implementation.
46 | //
47 |
48 | #ifndef LITTLE_ENDIAN
49 | # ifdef _WIN32
50 | # define LITTLE_ENDIAN 1 // Windows is always little-endian
51 | # elif __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
52 | # define LITTLE_ENDIAN 1 // GCC/Clang preprocessor check
53 | # else
54 | # define LITTLE_ENDIAN 0 // Otherwise don't assume little-endian
55 | # endif // _WIN32
56 | #endif // !LITTLE_ENDIAN
57 |
58 |
59 | //
60 | // Local types...
61 | //
62 |
63 | typedef uint64_t tKeccakLane; // @private@
64 |
65 |
66 | //
67 | // Local functions...
68 | //
69 |
70 | static void KeccakF1600_StatePermute(void *state);
71 |
72 |
73 | //
74 | // 'hcSHA3Final()' - Finalize the SHA3-256 hash, putting the result in "hash".
75 | //
76 |
77 | void
78 | hcSHA3Final(hc_sha3_t *ctx, // I - Hash context
79 | unsigned char *hash, // I - Hash buffer
80 | size_t hashlen) // I - Bytes to copy from hash
81 | {
82 | ctx->state[ctx->used] ^= 0x06;
83 | ctx->state[ctx->block - 1] ^= 0x80;
84 |
85 | KeccakF1600_StatePermute(ctx->state);
86 |
87 | if (hashlen < sizeof(ctx->state))
88 | {
89 | memcpy(hash, ctx->state, hashlen);
90 | }
91 | else
92 | {
93 | memcpy(hash, ctx->state, sizeof(ctx->state));
94 | memset(hash + sizeof(ctx->state), 0, hashlen - sizeof(ctx->state));
95 | }
96 | }
97 |
98 |
99 | //
100 | // 'hcSHA3Init()' - Initialize a SHA3-256 hashing context.
101 | //
102 |
103 | void
104 | hcSHA3Init(hc_sha3_t *ctx) // I - Hash context
105 | {
106 | memset(ctx, 0, sizeof(hc_sha3_t));
107 | ctx->block = 72;
108 | }
109 |
110 |
111 | //
112 | // 'hcSHA3Update()' - Update the SHA3-256 hashing context with the given data.
113 | //
114 |
115 | void
116 | hcSHA3Update(hc_sha3_t *ctx, // I - Hash context
117 | const void *data, // I - Data to hash
118 | size_t datalen) // I - Number of bytes of data
119 | {
120 | const unsigned char *dataptr; // Pointer into data
121 |
122 |
123 | dataptr = (const unsigned char *)data;
124 |
125 | while (datalen > 0)
126 | {
127 | while (ctx->used < ctx->block && datalen > 0)
128 | {
129 | ctx->state[ctx->used++] ^= *dataptr++;
130 | datalen --;
131 | }
132 |
133 | if (ctx->used == ctx->block)
134 | {
135 | KeccakF1600_StatePermute(ctx->state);
136 | ctx->used = 0;
137 | }
138 | }
139 | }
140 |
141 |
142 |
143 | #if !LITTLE_ENDIAN
144 | //
145 | // 'load64()' - Load a 64-bit value using the little-endian (LE) convention.
146 | //
147 | // On a LE platform, this can be greatly simplified using a cast.
148 | //
149 |
150 | static uint64_t // O - 64-bit integer value
151 | load64(const uint8_t *x) // I - Value buffer
152 | {
153 | int i; // Looping var
154 | uint64_t u = 0; // 64-bit value
155 |
156 |
157 | for (i = 7; i >= 0; i --)
158 | {
159 | u <<= 8;
160 | u |= x[i];
161 | }
162 |
163 | return u;
164 | }
165 |
166 | //
167 | // 'store64()' - Store a 64-bit value using the little-endian (LE) convention.
168 | //
169 | // On a LE platform, this can be greatly simplified using a cast.
170 | //
171 |
172 | static void
173 | store64(uint8_t *x, // I - Value buffer
174 | uint64_t u) // I - 64-bit integer value to store
175 | {
176 | unsigned int i; // Looping var
177 |
178 |
179 | for (i = 0; i < 8; i ++)
180 | {
181 | x[i] = u;
182 | u >>= 8;
183 | }
184 | }
185 |
186 |
187 | //
188 | // 'xor64()' - XOR into a 64-bit value using the little-endian (LE) convention.
189 | //
190 | // On a LE platform, this can be greatly simplified using a cast.
191 | //
192 |
193 | static void
194 | xor64(uint8_t *x, // I - Value buffer
195 | uint64_t u) // I - 64-bit integer value to XOR
196 | {
197 | unsigned int i; // Looping var
198 |
199 | for (i = 0; i < 8; i ++)
200 | {
201 | x[i] ^= u;
202 | u >>= 8;
203 | }
204 | }
205 | #endif // !LITTLE_ENDIAN
206 |
207 |
208 | //
209 | // Helper macros...
210 | //
211 |
212 | #define MIN(a, b) ((a) < (b) ? (a) : (b))
213 | #define ROL64(a, offset) ((((uint64_t)a) << offset) ^ (((uint64_t)a) >> (64-offset)))
214 | #define i(x, y) ((x)+5*(y))
215 |
216 | #if LITTLE_ENDIAN
217 | # define readLane(x, y) (((tKeccakLane*)state)[i(x, y)])
218 | # define writeLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) = (lane)
219 | # define XORLane(x, y, lane) (((tKeccakLane*)state)[i(x, y)]) ^= (lane)
220 | #else
221 | # define readLane(x, y) load64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y))
222 | # define writeLane(x, y, lane) store64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y), lane)
223 | # define XORLane(x, y, lane) xor64((uint8_t*)state+sizeof(tKeccakLane)*i(x, y), lane)
224 | #endif
225 |
226 |
227 | //
228 | // 'LFSR86540()' - Compute the linear feedback shift register (LFSR) used to
229 | // define the round constants (see [Keccak Reference, Section
230 | // 1.2]).
231 | //
232 |
233 | static int // O - Feedback result
234 | LFSR86540(uint8_t *LFSR) // I - Shift register
235 | {
236 | int result = ((*LFSR) & 0x01) != 0; // Feedback result
237 |
238 |
239 | if (((*LFSR) & 0x80) != 0)
240 | {
241 | // Primitive polynomial over GF(2): x^8+x^6+x^5+x^4+1
242 | (*LFSR) = (uint8_t)(((*LFSR) << 1) ^ 0x71);
243 | }
244 | else
245 | {
246 | (*LFSR) <<= 1;
247 | }
248 |
249 | return (result);
250 | }
251 |
252 |
253 | //
254 | // 'KeccakF1600_StatePermute()' - Compute the Keccak-f[1600] permutation on the
255 | // given state.
256 | //
257 |
258 | static void
259 | KeccakF1600_StatePermute(void *state) // I - State
260 | {
261 | unsigned int round, x, y, j, t; // Looping var
262 | uint8_t LFSRstate = 0x01; // Shift register
263 |
264 |
265 | for (round = 0; round < 24; round ++)
266 | {
267 | { // === θ step (see [Keccak Reference, Section 2.3.2]) ===
268 | tKeccakLane C[5], D;
269 |
270 | // Compute the parity of the columns
271 | for (x = 0; x < 5; x ++)
272 | C[x] = readLane(x, 0) ^ readLane(x, 1) ^ readLane(x, 2) ^ readLane(x, 3) ^ readLane(x, 4);
273 |
274 | for (x = 0; x < 5; x ++)
275 | {
276 | // Compute the θ effect for a given column
277 | D = C[(x+4)%5] ^ ROL64(C[(x+1)%5], 1);
278 |
279 | // Add the θ effect to the whole column
280 | for (y=0; y<5; y++)
281 | XORLane(x, y, D);
282 | }
283 | }
284 |
285 | { // === ρ and π steps (see [Keccak Reference, Sections 2.3.3 and 2.3.4]) ===
286 | tKeccakLane current, temp;
287 |
288 | // Start at coordinates (1 0)
289 | x = 1; y = 0;
290 | current = readLane(x, y);
291 |
292 | // Iterate over ((0 1)(2 3))^t * (1 0) for 0 ≤ t ≤ 23
293 | for (t = 0; t < 24; t ++)
294 | {
295 | // Compute the rotation constant r = (t+1)(t+2)/2
296 | unsigned int r = ((t+1)*(t+2)/2)%64;
297 |
298 | // Compute ((0 1)(2 3)) * (x y)
299 | unsigned int Y = (2*x+3*y)%5; x = y; y = Y;
300 |
301 | // Swap current and state(x,y), and rotate
302 | temp = readLane(x, y);
303 | writeLane(x, y, ROL64(current, r));
304 | current = temp;
305 | }
306 | }
307 |
308 | { // === χ step (see [Keccak Reference, Section 2.3.1]) ===
309 | tKeccakLane temp[5];
310 |
311 | for (y = 0; y < 5; y ++)
312 | {
313 | // Take a copy of the plane
314 | for (x = 0; x < 5; x ++)
315 | temp[x] = readLane(x, y);
316 |
317 | // Compute χ on the plane
318 | for (x = 0; x < 5; x ++)
319 | writeLane(x, y, temp[x] ^((~temp[(x+1)%5]) & temp[(x+2)%5]));
320 | }
321 | }
322 |
323 | { // === ι step (see [Keccak Reference, Section 2.3.5]) ===
324 | for (j = 0; j < 7; j ++)
325 | {
326 | unsigned int bitPosition = (1<root)
40 | html->root = NULL;
41 |
42 | html_remove(node);
43 |
44 | for (current = node->value.element.first_child; current; current = next)
45 | {
46 | /*
47 | * Get the next node...
48 | */
49 |
50 | if ((next = hcNodeGetFirstChildNode(current)) != NULL)
51 | {
52 | /*
53 | * Free parent nodes after child nodes have been freed...
54 | */
55 |
56 | current->value.element.first_child = NULL;
57 | continue;
58 | }
59 |
60 | if ((next = current->next_sibling) == NULL)
61 | {
62 | /*
63 | * Next node is the parent, which we'll free as needed...
64 | */
65 |
66 | if ((next = current->parent) == node)
67 | next = NULL;
68 | }
69 |
70 | /*
71 | * Free child...
72 | */
73 |
74 | html_delete(current);
75 | }
76 |
77 | /*
78 | * Then free the memory used by the parent node...
79 | */
80 |
81 | html_delete(node);
82 | }
83 |
84 |
85 | //
86 | // 'hcNodeGetComment()' - Get a HTML node's comment value, if any.
87 | //
88 |
89 | const char * // O - Comment value
90 | hcNodeGetComment(hc_node_t *node) // I - HTML node
91 | {
92 | return (node && node->element == HC_ELEMENT_COMMENT ? node->value.comment : NULL);
93 | }
94 |
95 |
96 | //
97 | // 'hcHTMLGetDOCTYPE()' - Get a HTML document's DOCTYPE value, if any.
98 | //
99 |
100 | const char * // O - DOCTYPE value
101 | hcHTMLGetDOCTYPE(hc_html_t *html) // I - HTML document
102 | {
103 | return (html && html->root ? hcNodeAttrGetNameValue(html->root, "") : NULL);
104 | }
105 |
106 |
107 | //
108 | // 'hcNodeGetElement()' - Get a HTML node's element/type.
109 | //
110 |
111 | hc_element_t // O - Node element/type
112 | hcNodeGetElement(hc_node_t *node) // I - HTML node
113 | {
114 | return (node ? node->element : HC_ELEMENT_WILDCARD);
115 | }
116 |
117 |
118 | //
119 | // 'hcNodeGetFirstChildNode()' - Get a HTML node's first child node, if any.
120 | //
121 |
122 | hc_node_t * // O - First child node or `NULL` if none
123 | hcNodeGetFirstChildNode(hc_node_t *node)// I - HTML node
124 | {
125 | return (node && node->element >= HC_ELEMENT_DOCTYPE ? node->value.element.first_child : NULL);
126 | }
127 |
128 |
129 | //
130 | // 'hcNodeGetLastChildNode()' - Get a HTML node's last child node, if any.
131 | //
132 |
133 | hc_node_t * // O - Last child node or `NULL` if none
134 | hcNodeGetLastChildNode(hc_node_t *node) // I - HTML node
135 | {
136 | return (node && node->element >= HC_ELEMENT_DOCTYPE ? node->value.element.last_child : NULL);
137 | }
138 |
139 |
140 | //
141 | // 'hcNodeGetNextSiblingNode()' - Get a HTML node's next sibling node, if any.
142 | //
143 |
144 | hc_node_t * // O - Next sibling node or `NULL` if none
145 | hcNodeGetNextSiblingNode(
146 | hc_node_t *node) // I - HTML node
147 | {
148 | return (node ? node->next_sibling : NULL);
149 | }
150 |
151 |
152 | //
153 | // 'hcNodeGetParentNode()' - Get a HTML node's parent node, if any.
154 | //
155 |
156 | hc_node_t * // O - Parent node or `NULL` if none
157 | hcNodeGetParentNode(hc_node_t *node) // I - HTML node
158 | {
159 | return (node ? node->parent : NULL);
160 | }
161 |
162 |
163 | //
164 | // 'hcNodeGetPrevSiblingNode()' - Get a HTML node's previous sibling node, if any.
165 | //
166 |
167 | hc_node_t * // O - Previous sibling node or `NULL` if none
168 | hcNodeGetPrevSiblingNode(
169 | hc_node_t *node) // I - HTML node
170 | {
171 | return (node ? node->prev_sibling : NULL);
172 | }
173 |
174 |
175 | //
176 | // 'hcHTMLGetRootNode()' - Get the root node for a document.
177 | //
178 |
179 | hc_node_t * // O - Root node or `NULL` if none
180 | hcHTMLGetRootNode(hc_html_t *html) // I - HTML document
181 | {
182 | return (html ? html->root : NULL);
183 | }
184 |
185 |
186 | //
187 | // 'hcNodeGetString()' - Get a HTML node's string value, if any.
188 | //
189 |
190 | const char * // O - String value
191 | hcNodeGetString(hc_node_t *node) // I - HTML node
192 | {
193 | return (node && node->element == HC_ELEMENT_STRING ? node->value.string : NULL);
194 | }
195 |
196 |
197 | //
198 | // 'hcNodeNewComment()' - Create a new HTML comment node.
199 | //
200 |
201 | hc_node_t * // O - New HTML comment node
202 | hcNodeNewComment(hc_node_t *parent, // I - Parent node
203 | const char *c) // I - Comment value
204 | {
205 | if (!parent || !c)
206 | return (NULL);
207 |
208 | return (html_new(parent, HC_ELEMENT_COMMENT, c));
209 | }
210 |
211 |
212 | //
213 | // 'hcNodeNewElement()' - Create a new HTML element node.
214 | //
215 |
216 | hc_node_t * // O - New HTML element node
217 | hcNodeNewElement(hc_node_t *parent, // I - Parent node
218 | hc_element_t element) // I - HTML element
219 | {
220 | if (!parent || element <= HC_ELEMENT_DOCTYPE || element >= HC_ELEMENT_MAX)
221 | return (NULL);
222 |
223 | return (html_new(parent, element, NULL));
224 | }
225 |
226 |
227 | //
228 | // 'hcHTMLNewRootNode()' - Create a new root node.
229 | //
230 |
231 | hc_node_t * // O - New root node
232 | hcHTMLNewRootNode(hc_html_t *html, // I - HTML document
233 | const char *doctype) // I - DOCTYPE value
234 | {
235 | hc_node_t *node; // New node
236 |
237 |
238 | if (!html || html->root || !doctype)
239 | return (NULL);
240 |
241 | if ((node = html_new(NULL, HC_ELEMENT_DOCTYPE, NULL)) != NULL)
242 | {
243 | html->root = node;
244 | node->value.element.html = html;
245 |
246 | hcNodeAttrSetNameValue(node, "", doctype);
247 | }
248 |
249 | return (node);
250 | }
251 |
252 |
253 | //
254 | // 'hcNodeNewString()' - Create a new HTML string node.
255 | //
256 |
257 | hc_node_t * // O - New HTML string node
258 | hcNodeNewString(hc_node_t *parent, // I - Parent node
259 | const char *s) // I - String value
260 | {
261 | if (!parent || !s)
262 | return (NULL);
263 |
264 | return (html_new(parent, HC_ELEMENT_STRING, s));
265 | }
266 |
267 |
268 | //
269 | // '_hcNodeNewUnknown()' - Create a new unknown HTML element or processing
270 | // directive node.
271 | //
272 |
273 | hc_node_t * // O - New HTML unknown node
274 | _hcNodeNewUnknown(hc_node_t *parent, // I - Parent node
275 | const char *unk) // I - Unknown value (excluding "<>")
276 | {
277 | if (!parent || !unk)
278 | return (NULL);
279 |
280 | return (html_new(parent, HC_ELEMENT_UNKNOWN, unk));
281 | }
282 |
283 |
284 | //
285 | // 'html_delete()' - Free a HTML node.
286 | //
287 |
288 | static void
289 | html_delete(hc_node_t *node) // I - HTML node
290 | {
291 | if (node->element >= HC_ELEMENT_DOCTYPE)
292 | hcDictDelete(node->value.element.attrs);
293 |
294 | free(node);
295 | }
296 |
297 |
298 | //
299 | // 'html_new()' - Create a new HTML node.
300 | //
301 |
302 | static hc_node_t * // O - New node or `NULL` on error
303 | html_new(hc_node_t *parent, // I - Parent node or `NULL` if root node
304 | hc_element_t element, // I - Element/node type
305 | const char *s) // I - String, if any
306 | {
307 | hc_node_t *node; // New node
308 | size_t nodesize; // Node size
309 | size_t slen = s ? strlen(s) : 0;
310 | // Length of string
311 |
312 |
313 | if (parent && parent->element < HC_ELEMENT_DOCTYPE)
314 | return (NULL);
315 |
316 | if (element < HC_ELEMENT_DOCTYPE)
317 | nodesize = sizeof(hc_node_t) - sizeof(node->value) + slen + 1;
318 | else
319 | nodesize = sizeof(hc_node_t);
320 |
321 | if ((node = (hc_node_t *)calloc(1, nodesize)) != NULL)
322 | {
323 | node->element = element;
324 | node->parent = parent;
325 |
326 | if (s && slen > 0)
327 | {
328 | if (element == HC_ELEMENT_STRING)
329 | memcpy(node->value.string, s, slen);
330 | else if (element == HC_ELEMENT_COMMENT)
331 | memcpy(node->value.comment, s, slen);
332 | else if (element == HC_ELEMENT_UNKNOWN)
333 | memcpy(node->value.unknown, s, slen);
334 | }
335 |
336 | if (parent)
337 | {
338 | if (element > HC_ELEMENT_DOCTYPE)
339 | node->value.element.html = parent->value.element.html;
340 |
341 | if (parent->value.element.last_child)
342 | {
343 | node->prev_sibling = parent->value.element.last_child;
344 | parent->value.element.last_child->next_sibling = node;
345 | parent->value.element.last_child = node;
346 | }
347 | else
348 | {
349 | parent->value.element.first_child = node;
350 | parent->value.element.last_child = node;
351 | }
352 | }
353 | }
354 |
355 | return (node);
356 | }
357 |
358 |
359 | //
360 | // 'html_remove()' - Remove a HTML node from its parent.
361 | //
362 |
363 | static void
364 | html_remove(hc_node_t *node) // I - HTML node
365 | {
366 | if (node->parent)
367 | {
368 | if (node->prev_sibling)
369 | node->prev_sibling->next_sibling = node->next_sibling;
370 | else
371 | node->parent->value.element.first_child = node->next_sibling;
372 |
373 | if (node->next_sibling)
374 | node->next_sibling->prev_sibling = node->prev_sibling;
375 | else
376 | node->parent->value.element.last_child = node->prev_sibling;
377 |
378 | node->parent = NULL;
379 | node->prev_sibling = NULL;
380 | node->next_sibling = NULL;
381 | }
382 | }
383 |
--------------------------------------------------------------------------------
/css.h:
--------------------------------------------------------------------------------
1 | //
2 | // CSS header file for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #ifndef HTMLCSS_CSS_H
13 | # define HTMLCSS_CSS_H
14 | # include "dict.h"
15 | # include "file.h"
16 | # include "font.h"
17 | # ifdef __cplusplus
18 | extern "C" {
19 | # endif // __cplusplus
20 |
21 |
22 | //
23 | // Constants...
24 | //
25 |
26 | # define HC_LENGTH_AUTO -999999999.0f
27 | // Automatic length
28 |
29 |
30 | //
31 | // Types...
32 | //
33 |
34 | typedef enum
35 | {
36 | HC_BACKGROUND_ATTACHMENT_SCROLL,
37 | HC_BACKGROUND_ATTACHMENT_FIXED
38 | } hc_background_attachment_t;
39 |
40 | typedef enum
41 | {
42 | HC_BACKGROUND_BOX_BORDER_BOX,
43 | HC_BACKGROUND_BOX_PADDING_BOX,
44 | HC_BACKGROUND_BOX_CONTENT_BOX
45 | } hc_background_box_t;
46 |
47 | typedef enum
48 | {
49 | HC_BACKGROUND_REPEAT_NO_REPEAT,
50 | HC_BACKGROUND_REPEAT_REPEAT,
51 | HC_BACKGROUND_REPEAT_REPEAT_X,
52 | HC_BACKGROUND_REPEAT_REPEAT_Y
53 | } hc_background_repeat_t;
54 |
55 | typedef enum
56 | {
57 | HC_BORDER_COLLAPSE_SEPARATE,
58 | HC_BORDER_COLLAPSE_COLLAPSE
59 | } hc_border_collapse_t;
60 |
61 | typedef enum
62 | {
63 | HC_BORDER_IMAGE_REPEAT_STRETCH,
64 | HC_BORDER_IMAGE_REPEAT_REPEAT,
65 | HC_BORDER_IMAGE_REPEAT_ROUND,
66 | HC_BORDER_IMAGE_REPEAT_SPACE
67 | } hc_border_image_repeat_t;
68 |
69 |
70 | typedef enum
71 | {
72 | HC_BORDER_STYLE_HIDDEN,
73 | HC_BORDER_STYLE_NONE,
74 | HC_BORDER_STYLE_DOTTED,
75 | HC_BORDER_STYLE_DASHED,
76 | HC_BORDER_STYLE_SOLID,
77 | HC_BORDER_STYLE_DOUBLE,
78 | HC_BORDER_STYLE_GROOVE,
79 | HC_BORDER_STYLE_RIDGE,
80 | HC_BORDER_STYLE_INSET,
81 | HC_BORDER_STYLE_OUTSET
82 | } hc_border_style_t;
83 |
84 | typedef enum
85 | {
86 | HC_BREAK_AUTO, // Break as needed
87 | HC_BREAK_ALWAYS, // Always break
88 | HC_BREAK_AVOID, // Avoid a break
89 | HC_BREAK_LEFT, // Break to the next left-hand page
90 | HC_BREAK_RIGHT // Break to the next right-hand page
91 | } hc_break_t;
92 |
93 | typedef enum
94 | {
95 | HC_CAPTION_SIDE_TOP,
96 | HC_CAPTION_SIDE_BOTTOM
97 | } hc_caption_side_t;
98 |
99 | typedef enum
100 | {
101 | HC_DIRECTION_LTR, // Left to right
102 | HC_DIRECTION_RTL // Right to left
103 | } hc_direction_t;
104 |
105 | typedef enum
106 | {
107 | HC_DISPLAY_NONE,
108 | HC_DISPLAY_BLOCK,
109 | HC_DISPLAY_INLINE,
110 | HC_DISPLAY_INLINE_BLOCK,
111 | HC_DISPLAY_INLINE_TABLE,
112 | HC_DISPLAY_LIST_ITEM,
113 | HC_DISPLAY_TABLE,
114 | HC_DISPLAY_TABLE_CAPTION,
115 | HC_DISPLAY_TABLE_HEADER_GROUP,
116 | HC_DISPLAY_TABLE_FOOTER_GROUP,
117 | HC_DISPLAY_TABLE_ROW_GROUP,
118 | HC_DISPLAY_TABLE_ROW,
119 | HC_DISPLAY_TABLE_COLUMN_GROUP,
120 | HC_DISPLAY_TABLE_COLUMN,
121 | HC_DISPLAY_TABLE_CELL
122 | } hc_display_t;
123 |
124 | typedef enum
125 | {
126 | HC_EMPTY_CELLS_HIDE,
127 | HC_EMPTY_CELLS_SHOW
128 | } hc_empty_cells_t;
129 |
130 | typedef enum
131 | {
132 | HC_FLOAT_NONE,
133 | HC_FLOAT_LEFT,
134 | HC_FLOAT_RIGHT
135 | } hc_float_t;
136 |
137 | typedef enum
138 | {
139 | HC_LIST_STYLE_POSITION_INSIDE,
140 | HC_LIST_STYLE_POSITION_OUTSIDE
141 | } hc_list_style_position_t;
142 |
143 | typedef enum
144 | {
145 | HC_LIST_STYLE_TYPE_DISC,
146 | HC_LIST_STYLE_TYPE_CIRCLE,
147 | HC_LIST_STYLE_TYPE_SQUARE,
148 | HC_LIST_STYLE_TYPE_DECIMAL,
149 | HC_LIST_STYLE_TYPE_DECIMAL_LEADING_ZERO,
150 | HC_LIST_STYLE_TYPE_LOWER_ROMAN,
151 | HC_LIST_STYLE_TYPE_UPPER_ROMAN,
152 | HC_LIST_STYLE_TYPE_LOWER_GREEK,
153 | HC_LIST_STYLE_TYPE_LOWER_LATIN,
154 | HC_LIST_STYLE_TYPE_UPPER_LATIN,
155 | HC_LIST_STYLE_TYPE_ARMENIAN,
156 | HC_LIST_STYLE_TYPE_GEORGIAN,
157 | HC_LIST_STYLE_TYPE_LOWER_ALPHA,
158 | HC_LIST_STYLE_TYPE_UPPER_ALPHA,
159 | HC_LIST_STYLE_TYPE_NONE
160 | } hc_list_style_type_t;
161 |
162 | typedef enum
163 | {
164 | HC_OVERFLOW_HIDDEN,
165 | HC_OVERFLOW_VISIBLE,
166 | HC_OVERFLOW_SCROLL,
167 | HC_OVERFLOW_AUTO
168 | } hc_overflow_t;
169 |
170 | typedef enum
171 | {
172 | HC_TABLE_LAYOUT_AUTO,
173 | HC_TABLE_LAYOUT_FIXED
174 | } hc_table_layout_t;
175 |
176 | typedef enum
177 | {
178 | HC_TEXT_ALIGN_LEFT,
179 | HC_TEXT_ALIGN_RIGHT,
180 | HC_TEXT_ALIGN_CENTER,
181 | HC_TEXT_ALIGN_JUSTIFY
182 | } hc_text_align_t;
183 |
184 | typedef enum
185 | {
186 | HC_TEXT_DECORATION_NONE,
187 | HC_TEXT_DECORATION_UNDERLINE,
188 | HC_TEXT_DECORATION_OVERLINE,
189 | HC_TEXT_DECORATION_LINE_THROUGH
190 | } hc_text_decoration_t;
191 |
192 | typedef enum
193 | {
194 | HC_TEXT_TRANSFORM_NONE,
195 | HC_TEXT_TRANSFORM_CAPITALIZE,
196 | HC_TEXT_TRANSFORM_LOWERCASE,
197 | HC_TEXT_TRANSFORM_UPPERCASE
198 | } hc_text_transform_t;
199 |
200 | typedef enum
201 | {
202 | HC_UNICODE_BIDI_NORMAL,
203 | HC_UNICODE_BIDI_EMBED,
204 | HC_UNICODE_BIDI_OVERRIDE
205 | } hc_unicode_bidi_t;
206 |
207 | typedef enum
208 | {
209 | HC_WHITE_SPACE_NORMAL,
210 | HC_WHITE_SPACE_NOWRAP,
211 | HC_WHITE_SPACE_PRE,
212 | HC_WHITE_SPACE_PRE_LINE,
213 | HC_WHITE_SPACE_PRE_WRAP
214 | } hc_white_space_t;
215 |
216 | typedef struct hc_color_s // sRGBA color
217 | {
218 | float red; // Red, 0.0 to 1.0
219 | float green; // Green, 0.0 to 1.0
220 | float blue; // Blue, 0.0 to 1.0
221 | float alpha; // Alpha, 0.0 (transparent) to 1.0 (opaque)
222 | } hc_color_t;
223 |
224 | typedef struct hc_point_s // Point/coordinate
225 | {
226 | float left; // Horizontal position
227 | float top; // Vertical position
228 | } hc_point_t;
229 |
230 | typedef struct hc_size_s // Point/coordinate
231 | {
232 | float width; // Width
233 | float height; // Height
234 | } hc_size_t;
235 |
236 | typedef struct hc_border_props_s // CSS border properties
237 | {
238 | hc_color_t color; // Border color
239 | hc_border_style_t style; // Border style
240 | float width; // Border width
241 | } hc_border_props_t;
242 |
243 | typedef struct hc_box_shadow_s // Box shadow values
244 | {
245 | float horizontal_offset;
246 | float vertical_offset;
247 | float blur_radius;
248 | float spread_distance;
249 | hc_color_t color;
250 | bool inset;
251 | } hc_box_shadow_t;
252 |
253 | // Higher-level types */
254 |
255 |
256 | typedef enum // What to compute
257 | {
258 | HC_COMPUTE_BASE, // Base content
259 | HC_COMPUTE_BEFORE, // Content before element
260 | HC_COMPUTE_AFTER, // Content after element
261 | HC_COMPUTE_FIRST_LINE, // First line of block
262 | HC_COMPUTE_FIRST_LETTER // First letter of block
263 | } hc_compute_t;
264 |
265 | typedef struct hc_border_s // All CSS border properties
266 | {
267 | hc_border_props_t left;
268 | hc_border_props_t top;
269 | hc_border_props_t right;
270 | hc_border_props_t bottom;
271 | } hc_border_t;
272 |
273 | typedef struct hc_border_radius_s // CSS border-xxx-radius properties
274 | {
275 | hc_size_t bottom_left; // Bottom-left border radius
276 | hc_size_t bottom_right; // Bottom-right border radius
277 | hc_size_t top_left; // Top-left border radius
278 | hc_size_t top_right; // Top-right border radius
279 | } hc_border_radius_t;
280 |
281 | typedef struct hc_box_s // CSS box properties
282 | {
283 | hc_rect_t bounds; // Computed bounds
284 | hc_size_t size; // Computed size
285 | hc_rect_t clip; // Clip bounds
286 | hc_size_t max_size;
287 | hc_size_t min_size;
288 | hc_background_attachment_t
289 | background_attachment;
290 | hc_background_box_t background_clip;
291 | hc_color_t background_color;
292 | const char *background_image;
293 | hc_background_box_t background_origin;
294 | hc_point_t background_position;
295 | hc_background_repeat_t background_repeat;
296 | hc_size_t background_size;
297 | hc_border_t border;
298 | const char *border_image;
299 | bool border_image_fill;
300 | hc_rect_t border_image_outset;
301 | hc_border_image_repeat_t border_image_repeat[2];
302 | hc_rect_t border_image_slice;
303 | hc_rect_t border_image_width;
304 | hc_border_radius_t border_radius;
305 | hc_size_t border_spacing;
306 | hc_box_shadow_t box_shadow;
307 | hc_break_t break_after;
308 | hc_break_t break_before;
309 | hc_break_t break_inside;
310 | hc_float_t float_value;
311 | const char *list_style_image;
312 | hc_list_style_position_t
313 | list_style_position;
314 | hc_list_style_type_t list_style_type;
315 | hc_rect_t margin;
316 | int orphans;
317 | hc_overflow_t overflow;
318 | hc_rect_t padding;
319 | int widows;
320 | int z_index;
321 | } hc_box_t;
322 |
323 | typedef struct hc_media_s // CSS media properties
324 | {
325 | const char *type; // "print", "screen", etc.
326 | int color_bits; // Color bits
327 | int monochrome_bits;// Grayscale bits
328 | hc_rect_t margin; // Margins
329 | hc_size_t size; // Dimensions
330 | } hc_media_t;
331 |
332 | typedef struct hc_table_s // CSS table properties
333 | {
334 | hc_border_collapse_t border_collapse;
335 | hc_caption_side_t caption_side;
336 | hc_empty_cells_t empty_cells;
337 | hc_table_layout_t table_layout;
338 | } hc_table_t;
339 |
340 | typedef struct hc_text_s // CSS text properties
341 | {
342 | hc_color_t color;
343 | hc_direction_t direction;
344 | hc_font_t *font; // Loaded font
345 | const char *font_family;
346 | float font_size;
347 | float font_size_adjust;
348 | hc_font_stretch_t font_stretch;
349 | hc_font_style_t font_style;
350 | hc_font_variant_t font_variant;
351 | hc_font_weight_t font_weight;
352 | float letter_spacing;
353 | float line_height;
354 | const char *quotes[4];
355 | hc_text_align_t text_align;
356 | hc_text_decoration_t text_decoration;
357 | float text_indent;
358 | hc_text_transform_t text_transform;
359 | hc_unicode_bidi_t unicode_bidi;
360 | hc_white_space_t white_space;
361 | float word_spacing;
362 | } hc_text_t;
363 |
364 | typedef struct _hc_css_s hc_css_t; // CSS data
365 |
366 |
367 | //
368 | // Functions...
369 | //
370 |
371 | extern void hcCSSDelete(hc_css_t *css);
372 | extern hc_css_t *hcCSSNew(hc_pool_t *pool);
373 | extern bool hcCSSImport(hc_css_t *css, hc_file_t *file);
374 | extern bool hcCSSImportDefault(hc_css_t *css);
375 | extern void hcCSSSetErrorCallback(hc_css_t *css, hc_error_cb_t cb, void *ctx);
376 | extern void hcCSSSetURLCallback(hc_css_t *css, hc_url_cb_t cb, void *ctx);
377 | extern int hcCSSSetMedia(hc_css_t *css, const char *type, int color_bits, int grayscale_bits, float width, float height);
378 |
379 |
380 | # ifdef __cplusplus
381 | }
382 | # endif // __cplusplus
383 | #endif // !HTMLCSS_CSS_H
384 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | Contributing to HTMLCSS
2 | =====================
3 |
4 | HTMLCSS is developed and distributed as open source software under the Apache
5 | License, Version 2.0. Contributions should be submitted as pull requests on
6 | the Github site:
7 |
8 | http://github.com/michaelrsweet/htmlcss/pulls
9 |
10 |
11 | Contents
12 | --------
13 |
14 | - [Build System](#build-system)
15 | - [Version Numbering](#version-numbering)
16 | - [Coding Guidelines](#coding-guidelines)
17 | - [Source Files](#source-files)
18 | - [Header Files](#header-files)
19 | - [Comments](#comments)
20 | - [Indentation](#indentation)
21 | - [Spacing](#spacing)
22 | - [Return Values](#return-values)
23 | - [Functions](#functions)
24 | - [Variables](#variables)
25 | - [Types](#types)
26 | - [Structures](#structures)
27 | - [Constants](#constants)
28 | - [Shell Script Guidelines](#shell-script-guidelines)
29 | - [Makefile Guidelines](#makefile-guidelines)
30 | - [General Organization](#general-organization)
31 | - [Makefile Documentation](#makefile-documentation)
32 | - [Portable Makefile Construction](#portable-makefile-construction)
33 | - [Standard Variables](#standard-variables)
34 | - [Standard Targets](#standard-targets)
35 | - [Object Files](#object-files)
36 | - [Programs](#programs)
37 | - [Static Libraries](#static-libraries)
38 | - [Shared Libraries](#shared-libraries)
39 | - [Dependencies](#dependencies)
40 | - [Install/Uninstall Support](#installuninstall-support)
41 |
42 |
43 | Build System
44 | ------------
45 |
46 | The build system uses [GNU autoconf][AUTOCONF] to create a simple POSIX makefile
47 | to build static and/or shared libraries. To improve portability, makefiles
48 | *must not* make use of features unique to GNU make. See the
49 | [Makefile Guidelines](#makefile-guidelines) section for a description of the
50 | allowed make features and makefile guidelines.
51 |
52 | An Xcode project is provided for macOS/iOS developers, and a Visual Studio
53 | solution and projects for Windows developers.
54 |
55 | [AUTOCONF]: https://www.gnu.org/software/autoconf/
56 |
57 |
58 | Version Numbering
59 | -----------------
60 |
61 | HTMLCSS uses a three-part version number separated by periods to represent the
62 | major, minor, and patch release numbers. Major release numbers indicate large
63 | design changes or backwards-incompatible changes to the library. Minor release
64 | numbers indicate new features and other smaller changes which are backwards-
65 | compatible with previous releases. Patch numbers indicate bug fixes to the
66 | previous feature or patch release.
67 |
68 | Production releases use the plain version numbers:
69 |
70 | MAJOR.MINOR.PATCH
71 | 1.0.0
72 | 1.0.1
73 | 1.0.2
74 | ...
75 | 1.1.0
76 | ...
77 | 2.0.0
78 |
79 | The first production release in a MAJOR.MINOR series (MAJOR.MINOR.0) is called
80 | a feature release. Feature releases are the only releases that may contain new
81 | features. Subsequent production releases in a MAJOR.MINOR series may only
82 | contain bug fixes.
83 |
84 | Beta-test releases are identified by appending the letter B to the major and
85 | minor version numbers followed by the beta release number:
86 |
87 | MAJOR.MINORbNUMBER
88 | 1.0b1
89 |
90 | Release candidates are identified by appending the letters RC to the major and
91 | minor version numbers followed by the release candidate number:
92 |
93 | MAJOR.MINORrcNUMBER
94 | 1.0rc1
95 |
96 | > Note: While the beta/release candidate syntax is *not* strictly compatible
97 | > with [Semantic Versioning](https://semver.org), it is better supported by the
98 | > various traditional package formats. When packaging a pre-release version of
99 | > HTMLCSS in a format that requires the use of semantic version numbers, the
100 | > version number should simply be converted to the form "MAJOR.MINOR.0-suffix".
101 |
102 |
103 | Coding Guidelines
104 | -----------------
105 |
106 | Contributed source code must follow the guidelines below. While the examples
107 | are for C source files, source code for other languages should conform to the
108 | same guidelines as allowed by the language.
109 |
110 |
111 | ### Source Files
112 |
113 | All source files names must be 16 characters or less in length to ensure
114 | compatibility with older UNIX filesystems. Source files containing functions
115 | have an extension of ".c" for C source files. All "include" files have an
116 | extension of ".h". Tabs are set to 8 characters or columns.
117 |
118 | The top of each source file contains a header giving the purpose or nature of
119 | the source file and the copyright and licensing notice:
120 |
121 | //
122 | // Description of file contents.
123 | //
124 | // Copyright © YYYY by AUTHOR.
125 | //
126 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
127 | // information.
128 | //
129 |
130 |
131 | ### Header Files
132 |
133 | Private API header files must be named with the suffix "-private", for example
134 | the "hc.h" header file defines all of the public APIs while the
135 | "hc-private.h" header file defines all of the private APIs. Typically a
136 | private API header file will include the corresponding public API header file.
137 |
138 |
139 | ### Comments
140 |
141 | All source code utilizes block comments within functions to describe the
142 | operations being performed by a group of statements; avoid putting a comment
143 | per line unless absolutely necessary, and then consider refactoring the code
144 | so that it is not necessary. C source files use the C99 comment format
145 | ("// comment"):
146 |
147 | // Clear the state array before we begin...
148 | for (i = 0; i < (sizeof(array) / sizeof(sizeof(array[0])); i ++)
149 | array[i] = HC_STATE_IDLE;
150 |
151 | // Wait for state changes on another thread...
152 | do
153 | {
154 | for (i = 0; i < (sizeof(array) / sizeof(sizeof(array[0])); i ++)
155 | if (array[i] != HC_STATE_IDLE)
156 | break;
157 |
158 | if (i == (sizeof(array) / sizeof(array[0])))
159 | sleep(1);
160 | } while (i == (sizeof(array) / sizeof(array[0])));
161 |
162 |
163 | ### Indentation
164 |
165 | All code blocks enclosed by brackets begin with the opening brace on a new
166 | line. The code then follows starting on a new line after the brace and is
167 | indented 2 spaces. The closing brace is then placed on a new line following
168 | the code at the original indentation:
169 |
170 | {
171 | int i; // Looping var
172 |
173 | // Process foobar values from 0 to 999...
174 | for (i = 0; i < 1000; i ++)
175 | {
176 | do_this(i);
177 | do_that(i);
178 | }
179 | }
180 |
181 | Single-line statements following "do", "else", "for", "if", and "while" are
182 | indented 2 spaces as well. Blocks of code in a "switch" block are indented 4
183 | spaces after each "case" and "default" case:
184 |
185 | switch (array[i])
186 | {
187 | case HC_STATE_IDLE :
188 | do_this(i);
189 | do_that(i);
190 | break;
191 |
192 | default :
193 | do_nothing(i);
194 | break;
195 | }
196 |
197 |
198 | ### Spacing
199 |
200 | A space follows each reserved word such as `if`, `while`, etc. Spaces are not
201 | inserted between a function name and the arguments in parenthesis.
202 |
203 |
204 | ### Return Values
205 |
206 | Parenthesis surround values returned from a function:
207 |
208 | return (HC_STATE_IDLE);
209 |
210 |
211 | ### Functions
212 |
213 | Functions with a global scope have a lowercase prefix followed by capitalized
214 | words, e.g., `hcDoThis`, `hcDoThat`, `hcDoSomethingElse`, etc. Private
215 | global functions begin with a leading underscore, e.g., `_hcDoThis`,
216 | `_hcDoThat`, etc.
217 |
218 | Functions with a local scope are declared static with lowercase names and
219 | underscores between words, e.g., `do_this`, `do_that`, `do_something_else`, etc.
220 |
221 | Function names follow the following pattern:
222 |
223 | - "hcFooCreate" to create a Foo object,
224 | - "hcFooDelete" to destroy (free) a Foo object,
225 | - "hcFooGetBar" to get data element Bar from object Foo,
226 | - "hcFooIsBar" to test condition Bar for object Foo, and
227 | - "hcFooSetBar" to set data element Bar in object Foo.
228 | - "hcFooVerb" to take an action with object Foo.
229 |
230 | Each function begins with a comment header describing what the function does,
231 | the possible input limits (if any), the possible output values (if any), and
232 | any special information needed:
233 |
234 | //
235 | // 'hcDoThis()' - Short description of function.
236 | //
237 | // Longer documentation for function with examples using a subset of
238 | // markdown. This is a bulleted list:
239 | //
240 | // - One fish
241 | // - Two fish
242 | // - Red fish
243 | // - Blue fish
244 | //
245 | // > *Note:* Special notes for developer should be markdown block quotes.
246 | //
247 |
248 | float // O - Inverse power value, 0.0 <= y <= 1.1
249 | hcDoThis(float x) // I - Power value (0.0 <= x <= 1.1)
250 | {
251 | ...
252 | return (y);
253 | }
254 |
255 | Return/output values are indicated using an "O" prefix, input values are
256 | indicated using the "I" prefix, and values that are both input and output use
257 | the "IO" prefix for the corresponding in-line comment.
258 |
259 | The [`codedoc` documentation generator][1] also understands the following
260 | special text in the function description comment:
261 |
262 | @deprecated@ - Marks the function as deprecated (not recommended
263 | for new development and scheduled for removal)
264 | @since version@ - Marks the function as new in the specified version.
265 | @private@ - Marks the function as private (same as starting the
266 | function name with an underscore)
267 |
268 | [1]: https://www.msweet.org/codedoc
269 |
270 |
271 | ### Variables
272 |
273 | Variables with a global scope are capitalized, e.g., `ThisVariable`,
274 | `ThatVariable`, `ThisStateVariable`, etc. Globals *must not* be used in the
275 | HTMLCSS library.
276 |
277 | Variables with a local scope are lowercase with underscores between words,
278 | e.g., `this_variable`, `that_variable`, etc. Any "local global" variables
279 | shared by functions within a source file are declared static.
280 |
281 | Each variable is declared on a separate line and is immediately followed by a
282 | comment block describing the variable:
283 |
284 | int ThisVariable; // The current state of this
285 | static int that_variable; // The current state of that
286 |
287 |
288 | ### Types
289 |
290 | All type names are lowercase with underscores between words and `_t` appended
291 | to the end of the name, e.g., `hc_this_type_t`, `hc_that_type_t`, etc.
292 | Type names start with the "hc\_" prefix to avoid conflicts with system types.
293 | Private type names start with an underscore, e.g., `_hc_this_t`,
294 | `_hc_that_t`, etc.
295 |
296 | Each type has a comment block immediately after the typedef:
297 |
298 | typedef int hc_this_type_t; // This type is for foobar options.
299 |
300 |
301 | ### Structures
302 |
303 | All structure names are lowercase with underscores between words and `_s`
304 | appended to the end of the name, e.g., `hc_this_s`, `hc_that_s`, etc.
305 | Structure names start with the "hc\_" prefix to avoid conflicts with system
306 | types. Private structure names start with an underscore, e.g., `_hc_this_s`,
307 | `_hc_that_s`, etc.
308 |
309 | Each structure has a comment block immediately after the struct and each member
310 | is documented similar to the variable naming policy above:
311 |
312 | struct hc_this_struct_s // This structure is for foobar options.
313 | {
314 | int this_member; // Current state for this
315 | int that_member; // Current state for that
316 | };
317 |
318 | One common design pattern is to define a private structure with a public
319 | typedef, for example:
320 |
321 | // In public header
322 | typedef struct _hc_foo_s hc_foo_t // Foo object
323 |
324 | // In private header
325 | struct _hc_foo_s // Foo object
326 | {
327 | int this_member; // Current state for this
328 | int that_member; // Current state for that
329 | };
330 |
331 |
332 | ### Constants
333 |
334 | All constant names are uppercase with underscores between words, e.g.,
335 | `HC_THIS_CONSTANT`, `HC_THAT_CONSTANT`, etc. Constants begin with the
336 | "HC\_" prefix to avoid conflicts with system constants. Private constants
337 | start with an underscore, e.g., `_HC_THIS_CONSTANT`,
338 | `_HC_THAT_CONSTANT`, etc.
339 |
340 | Typed enumerations should be used whenever possible to allow for type checking
341 | by the compiler. The constants for typed enumerations must match the type name
342 | in uppercase, for example a `hc_foo_e` enumeration has constant names
343 | starting with `HC_FOO_`.
344 |
345 | Comment blocks immediately follow each constant:
346 |
347 | typedef enum hc_style_e // Style enumerations
348 | {
349 | HC_STYLE_THIS, // This style
350 | HC_STYLE_THAT // That style
351 | } hc_style_t;
352 |
353 |
354 | Shell Script Guidelines
355 | -----------------------
356 |
357 | All shell scripts in HTMLCSS must conform to the [POSIX shell][POSIX-SHELL]
358 | command language and should restrict their dependence on non-POSIX utility
359 | commands.
360 |
361 | [POSIX-SHELL]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V3_chap02.html#tag_18
362 |
363 |
364 | Makefile Guidelines
365 | -------------------
366 |
367 | HTMLCSS uses a single [POSIX makefile][POSIX-MAKE] to build it. GNU make
368 | extensions MUST NOT be used.
369 |
370 | [POSIX-MAKE]: https://pubs.opengroup.org/onlinepubs/9699919799/utilities/make.html
371 |
372 | The following variables are defined in the makefile:
373 |
374 | - `AR`; the static library archiver command,
375 | - `ARFLAGS`; options for the static library archiver,
376 | - `CC`; the C compiler command,
377 | - `CFLAGS`; options for the C compiler,
378 | - `CODESIGN_IDENTITY`: the code signing identity,
379 | - `CPPFLAGS`; options for the C preprocessor,
380 | - `DESTDIR`/`DSTROOT`: the destination root directory when installing.
381 | - `DSO`; the shared library building command,
382 | - `DSOFLAGS`; options for the shared library building command,
383 | - `LDFLAGS`; options for the linker,
384 | - `LIBHC`: the name of the primary (shared or static) library
385 | - `LIBHC_STATIC`: the name of the secondary (static) library
386 | - `LIBS`; libraries for all programs,
387 | - `OPTIM`; common compiler optimization options,
388 | - `prefix`; the installation prefix directory,
389 | - `RANLIB`; the static library indexing command,
390 | - `SHELL`; the sh (POSIX shell) command,
391 | - `VERSION`: the library version number.
392 |
393 | The following standard targets are defined in the makefile:
394 |
395 | - `all`; creates the static library and unit test program.
396 | - `clean`; removes all target programs libraries, documentation files, and
397 | object files,
398 | - `install`; installs all distribution files in their corresponding locations.
399 | - `test`; runs the unit test program, building it as needed.
400 |
--------------------------------------------------------------------------------
/html-load.c:
--------------------------------------------------------------------------------
1 | //
2 | // HTML load functions for HTMLCSS library.
3 | //
4 | // https://github.com/michaelrsweet/htmlcss
5 | //
6 | // Copyright © 2018-2025 by Michael R Sweet.
7 | //
8 | // Licensed under Apache License v2.0. See the file "LICENSE" for more
9 | // information.
10 | //
11 |
12 | #include "html-private.h"
13 | #include "file-private.h"
14 | #include
15 |
16 |
17 | //
18 | // Local macros...
19 | //
20 |
21 | #define html_isblock(x) ((x) == HC_ELEMENT_ADDRESS || (x) == HC_ELEMENT_P || (x) == HC_ELEMENT_PRE || ((x) >= HC_ELEMENT_H1 && (x) <= HC_ELEMENT_H6) || (x) == HC_ELEMENT_HR || (x) == HC_ELEMENT_TABLE)
22 | #define html_isleaf(x) ((x) == HC_ELEMENT_AREA || (x) == HC_ELEMENT_BASE || (x) == HC_ELEMENT_BR || (x) == HC_ELEMENT_COL || (x) == HC_ELEMENT_EMBED || (x) == HC_ELEMENT_HR ||(x) == HC_ELEMENT_IMG || (x) == HC_ELEMENT_INPUT || (x) == HC_ELEMENT_ISINDEX || (x) == HC_ELEMENT_LINK || (x) == HC_ELEMENT_META || (x) == HC_ELEMENT_PARAM || (x) == HC_ELEMENT_SOURCE || (x) == HC_ELEMENT_SPACER || (x) == HC_ELEMENT_TRACK || (x) == HC_ELEMENT_WBR)
23 | #define html_islist(x) ((x) == HC_ELEMENT_DL || (x) == HC_ELEMENT_OL || (x) == HC_ELEMENT_UL || (x) == HC_ELEMENT_DIR || (x) == HC_ELEMENT_MENU)
24 | #define html_islentry(x) ((x) == HC_ELEMENT_LI || (x) == HC_ELEMENT_DD || (x) == HC_ELEMENT_DT)
25 | #define html_issuper(x) ((x) == HC_ELEMENT_CENTER || (x) == HC_ELEMENT_DIV || (x) == HC_ELEMENT_BLOCKQUOTE)
26 | #define html_istable(x) ((x) == HC_ELEMENT_TBODY || (x) == HC_ELEMENT_THEAD || (x) == HC_ELEMENT_TFOOT || (x) == HC_ELEMENT_TR)
27 | #define html_istentry(x) ((x) == HC_ELEMENT_TD || (x) == HC_ELEMENT_TH)
28 |
29 |
30 | //
31 | // Local functions...
32 | //
33 |
34 | static int html_parse_attr(hc_file_t *file, int ch, hc_node_t *node);
35 | static bool html_parse_comment(hc_file_t *file, hc_node_t **parent);
36 | static bool html_parse_doctype(hc_file_t *file, hc_html_t *html, hc_node_t **parent);
37 | static bool html_parse_element(hc_file_t *file, int ch, hc_html_t *html, hc_node_t **parent);
38 | static bool html_parse_unknown(hc_file_t *file, hc_node_t **parent, const char *unk);
39 |
40 |
41 | //
42 | // 'hcHTMLImport()' - Load a HTML file into a document.
43 | //
44 |
45 | bool // O - `true` on success, `false` on error
46 | hcHTMLImport(hc_html_t *html, // I - HTML document
47 | hc_file_t *file) // I - File to import
48 | {
49 | hc_node_t *parent = NULL; // Parent node
50 | bool status = true; // Load status
51 | int ch; // Current character
52 | char buffer[8192], // Temporary buffer
53 | *bufptr, // Pointer into buffer
54 | *bufend; // End of buffer
55 |
56 |
57 | // Range check input...
58 | if (!html || html->root || !file)
59 | return (false);
60 |
61 | // f.parent = NULL;
62 |
63 | // Parse file...
64 | bufptr = buffer;
65 | bufend = buffer + sizeof(buffer) - 1;
66 |
67 | while ((ch = hcFileGetc(file)) != EOF)
68 | {
69 | if (ch == '<')
70 | {
71 | // Read a HTML element...
72 | ch = hcFileGetc(file);
73 |
74 | if (isspace(ch) || ch == '=' || ch == '<')
75 | {
76 | // Sigh... "<" followed by anything but an element name is invalid
77 | // HTML, but many pages are still broken. Log it and abort if the error
78 | // callback says to...
79 | if (!_hcFileError(file, "Unquoted '<'."))
80 | {
81 | status = false;
82 | break;
83 | }
84 |
85 | if (bufptr >= (bufend - 1))
86 | {
87 | // Add text string...
88 | if (parent)
89 | {
90 | *bufptr = '\0';
91 | hcNodeNewString(parent, buffer);
92 | bufptr = buffer;
93 | }
94 | else
95 | {
96 | status = false;
97 | _hcFileError(file, "Text without leading element or directive.");
98 | break;
99 | }
100 | }
101 |
102 | *bufptr++ = '<';
103 |
104 | if (ch == '<')
105 | hcFileUngetc(file, ch);
106 | else
107 | *bufptr++ = (char)ch;
108 | }
109 | else
110 | {
111 | // Got the first character of an element name, add any pending text and
112 | // then parse the element...
113 | if (bufptr > buffer)
114 | {
115 | // Add text string...
116 | if (parent)
117 | {
118 | *bufptr = '\0';
119 | hcNodeNewString(parent, buffer);
120 | bufptr = buffer;
121 | }
122 | else
123 | {
124 | status = false;
125 | _hcFileError(file, "Text without leading element or directive.");
126 | break;
127 | }
128 | }
129 |
130 | if (!(status = html_parse_element(file, ch, html, &parent)))
131 | break;
132 | }
133 | }
134 | else
135 | {
136 | if (bufptr < bufend)
137 | *bufptr++ = (char)ch;
138 |
139 | if (ch == '\n' || bufptr >= bufend)
140 | {
141 | if (parent)
142 | {
143 | *bufptr = '\0';
144 | hcNodeNewString(parent, buffer);
145 | bufptr = buffer;
146 | }
147 | else
148 | {
149 | status = false;
150 | _hcFileError(file, "Text without leading element or directive.");
151 | }
152 | }
153 | }
154 |
155 | }
156 |
157 | if (bufptr > buffer)
158 | {
159 | // Add trailing text string...
160 | if (parent)
161 | {
162 | *bufptr = '\0';
163 | hcNodeNewString(parent, buffer);
164 | }
165 | else
166 | {
167 | status = false;
168 | _hcFileError(file, "Text without leading element or directive.");
169 | }
170 | }
171 |
172 | return (status);
173 | }
174 |
175 |
176 | //
177 | // 'html_parse_attr()' - Parse an attribute.
178 | //
179 |
180 | static int // O - Character or `EOF` to stop
181 | html_parse_attr(hc_file_t *file, // I - File to read from
182 | int ch, // I - Initial character
183 | hc_node_t *node) // I - HTML element node
184 | {
185 | char name[256], // Name string
186 | value[2048], // Value string
187 | *ptr, // Pointer into string
188 | *end; // End of string
189 |
190 |
191 | // Read name...
192 | ptr = name;
193 | end = name + sizeof(name) - 1;
194 |
195 | do
196 | {
197 | if (ptr < end)
198 | *ptr++ = (char)tolower(ch);
199 | else
200 | break;
201 | }
202 | while ((ch = hcFileGetc(file)) != EOF && ch != '=' && ch != '>' && !isspace(ch));
203 |
204 | *ptr = '\0';
205 |
206 | if (ch == '=')
207 | {
208 | // Read value...
209 | ptr = value;
210 | end = value + sizeof(value) - 1;
211 |
212 | if ((ch = hcFileGetc(file)) == '\'' || ch == '\"')
213 | {
214 | int quote = ch; // Quote character
215 |
216 | while ((ch = hcFileGetc(file)) != EOF && ch != quote)
217 | {
218 | if (ptr < end)
219 | *ptr++ = (char)ch;
220 | else
221 | break;
222 | }
223 | }
224 | else if (!isspace(ch) && ch != '>' && ch != EOF)
225 | {
226 | do
227 | {
228 | if (ptr < end)
229 | *ptr++ = (char)ch;
230 | else
231 | break;
232 | }
233 | while ((ch = hcFileGetc(file)) != EOF && ch != '>' && !isspace(ch));
234 | }
235 |
236 | *ptr = '\0';
237 | hcNodeAttrSetNameValue(node, name, value);
238 | }
239 | else if (ch != EOF)
240 | {
241 | // Add "name=name"...
242 | hcNodeAttrSetNameValue(node, name, name);
243 | }
244 |
245 | return (ch);
246 | }
247 |
248 |
249 | //
250 | // 'html_parse_comment()' - Parse a comment.
251 | //
252 |
253 | static bool // O - `true` to continue, `false` to stop
254 | html_parse_comment(hc_file_t *file, // I - File to read from
255 | hc_node_t **parent) // IO - Parent node
256 | {
257 | int ch; // Current character
258 | char buffer[8192], // String buffer
259 | *bufptr, // Pointer into buffer
260 | *bufend; // End of buffer
261 |
262 |
263 | bufptr = buffer;
264 | bufend = buffer + sizeof(buffer) - 1;
265 |
266 | while ((ch = hcFileGetc(file)) != EOF)
267 | {
268 | if (ch == '>' && bufptr > (buffer + 1) && bufptr[-1] == '-' && bufptr[-2] == '-')
269 | {
270 | // End of comment...
271 | bufptr -= 2;
272 | break;
273 | }
274 | else if (bufptr < bufend)
275 | *bufptr++ = (char)ch;
276 | else if (!_hcFileError(file, "Comment too long."))
277 | return (false);
278 | else
279 | break;
280 | }
281 |
282 | *bufptr = '\0';
283 |
284 | hcNodeNewComment(*parent, buffer);
285 |
286 | if (ch == EOF)
287 | return (_hcFileError(file, "Unexpected end-of-file."));
288 | else if (ch != '>')
289 | return (_hcFileError(file, "Comment too long."));
290 | else
291 | return (true);
292 | }
293 |
294 |
295 | //
296 | // 'html_parse_doctype()' - Parse a DOCTYPE element.
297 | //
298 |
299 | static bool // O - `true` to continue, `false` to stop
300 | html_parse_doctype(hc_file_t *file, // I - File to read from
301 | hc_html_t *html, // I - HTML document
302 | hc_node_t **parent) // IO - Parent node
303 | {
304 | int ch; // Character from file
305 | char buffer[2048], // String buffer
306 | *bufptr, // Pointer into buffer
307 | *bufend; // End of buffer
308 |
309 |
310 | bufptr = buffer;
311 | bufend = buffer + sizeof(buffer) - 1;
312 |
313 | while ((ch = hcFileGetc(file)) != EOF)
314 | {
315 | if (!isspace(ch))
316 | break;
317 | }
318 |
319 | while (ch != EOF && ch != '>')
320 | {
321 | if (bufptr < bufend)
322 | *bufptr++ = (char)ch;
323 | else
324 | break;
325 |
326 | if (ch == '\'' || ch == '\"')
327 | {
328 | int quote = ch; // Quote character
329 |
330 | while ((ch = hcFileGetc(file)) != EOF && ch != quote)
331 | {
332 | if (bufptr < bufend)
333 | *bufptr++ = (char)ch;
334 | else
335 | break;
336 | }
337 | }
338 | else
339 | ch = hcFileGetc(file);
340 | }
341 |
342 | *bufptr = '\0';
343 |
344 | if (ch == EOF)
345 | {
346 | _hcFileError(file, "Unexpected end-of-file.");
347 | return (false);
348 | }
349 | else if (ch != '>')
350 | _hcFileError(file, " too long.");
351 |
352 | *parent = hcHTMLNewRootNode(html, buffer);
353 |
354 | return (*parent != NULL);
355 | }
356 |
357 |
358 | //
359 | // 'html_parse_element()' - Parse an element.
360 | //
361 |
362 | static bool // O - `true` to continue, `false` to stop
363 | html_parse_element(hc_file_t *file, // I - File to read from
364 | int ch, // I - Initial character after '<'
365 | hc_html_t *html, // I - HTML document
366 | hc_node_t **parent) // IO - Parent node
367 | {
368 | char buffer[256], // String buffer
369 | *bufptr, // Pointer into buffer
370 | *bufend; // End of buffer
371 | hc_element_t element; // Element index
372 | hc_node_t *node; // New node
373 | bool close_el = ch == '/'; // Close element?
374 |
375 |
376 | // Read the element name...
377 | bufptr = buffer;
378 | bufend = buffer + sizeof(buffer) - 1;
379 | if (!close_el)
380 | *bufptr++ = (char)ch;
381 |
382 | while ((ch = hcFileGetc(file)) != EOF)
383 | {
384 | if (isspace(ch) || ch == '>' || ch == '/')
385 | break;
386 | else if (bufptr < bufend)
387 | *bufptr++ = (char)ch;
388 | else if (!_hcFileError(file, "Element name too long."))
389 | return (false);
390 | else
391 | break;
392 |
393 | if ((bufptr - buffer) == 3 && !memcmp(buffer, "!--", 3))
394 | {
395 | // Comment without whitespace, pretend we got some...
396 | ch = ' ';
397 | break;
398 | }
399 | }
400 |
401 | if (ch == EOF)
402 | {
403 | _hcFileError(file, "Unexpected end-of-file.");
404 | return (false);
405 | }
406 |
407 | *bufptr = '\0';
408 |
409 | // Convert the name to an enum...
410 | if (isspace(ch) || ch == '>' || ch == '/')
411 | {
412 | element = hcElementValue(buffer);
413 |
414 | if (element == HC_ELEMENT_UNKNOWN && !_hcFileError(file, "Unknown element '%s'.", buffer))
415 | return (false);
416 | }
417 | else
418 | {
419 | hcFileUngetc(file, ch);
420 | element = HC_ELEMENT_UNKNOWN;
421 | }
422 |
423 | // Parse unknown, comment, and doctype elements accordingly...
424 | if (element == HC_ELEMENT_DOCTYPE)
425 | {
426 | if (close_el)
427 | {
428 | _hcFileError(file, "Invalid !DOCTYPE> seem.");
429 | return (false);
430 | }
431 | else if (html->root)
432 | {
433 | _hcFileError(file, "Duplicate seen.");
434 | return (false);
435 | }
436 |
437 | return (html_parse_doctype(file, html, parent));
438 | }
439 | else if (!*parent)
440 | {
441 | if (!_hcFileError(file, "Missing directive."))
442 | return (false);
443 |
444 | *parent = hcHTMLNewRootNode(html, "html");
445 | }
446 | else if (element == HC_ELEMENT_UNKNOWN)
447 | {
448 | if (close_el)
449 | {
450 | char unk[257]; // Unknown value
451 |
452 | snprintf(unk, sizeof(unk), "/%s", buffer);
453 | return (html_parse_unknown(file, parent, unk));
454 | }
455 | else
456 | {
457 | return (html_parse_unknown(file, parent, buffer));
458 | }
459 | }
460 | else if (element == HC_ELEMENT_COMMENT)
461 | {
462 | return (html_parse_comment(file, parent));
463 | }
464 |
465 | // Otherwise add the element (or close it) in the right place...
466 | if (close_el)
467 | {
468 | // Close the specified element...
469 | if (ch != '>' && !_hcFileError(file, "Invalid %s> element.", buffer))
470 | return (false);
471 |
472 | for (node = *parent; node; node = node->parent)
473 | {
474 | if (node->element == element)
475 | break;
476 | }
477 |
478 | if (node)
479 | *parent = node->parent;
480 | else if (!_hcFileError(file, "Missing <%s> for %s> element.", buffer, buffer))
481 | return (false);
482 |
483 | return (true);
484 | }
485 |
486 | // HTML doesn't enforce strict open/close markup semantics, so allow ,
487 | //
, etc. to close out like markup...
488 | if (html_issuper(element))
489 | {
490 | for (node = *parent; node; node = node->parent)
491 | {
492 | if (html_istentry(node->element))
493 | break;
494 | }
495 | }
496 | else if (html_islist(element))
497 | {
498 | for (node = *parent; node; node = node->parent)
499 | {
500 | if (html_isblock(node->element) || html_islentry(node->element) || html_istentry(node->element) || html_issuper(node->element))
501 | break;
502 | }
503 | }
504 | else if (html_islentry(element))
505 | {
506 | for (node = *parent; node; node = node->parent)
507 | {
508 | if (html_islist(node->element))
509 | break;
510 | }
511 | }
512 | else if (html_isblock(element))
513 | {
514 | for (node = *parent; node; node = node->parent)
515 | {
516 | if (html_isblock(node->element))
517 | {
518 | node = node->parent;
519 | break;
520 | }
521 | else if (html_istentry(node->element) || html_islist(node->element) || html_islentry(node->element) || html_issuper(node->element))
522 | {
523 | break;
524 | }
525 | }
526 | }
527 | else if (element == HC_ELEMENT_THEAD || element == HC_ELEMENT_TBODY || element == HC_ELEMENT_TFOOT)
528 | {
529 | for (node = *parent; node; node = node->parent)
530 | {
531 | if (node->element == HC_ELEMENT_TABLE)
532 | break;
533 | }
534 | }
535 | else if (html_istentry(element))
536 | {
537 | for (node = *parent; node; node = node->parent)
538 | {
539 | if (html_istentry(node->element))
540 | {
541 | node = node->parent;
542 | break;
543 | }
544 | else if (node->element == HC_ELEMENT_TR)
545 | {
546 | break;
547 | }
548 | else if (node->element == HC_ELEMENT_TABLE || html_istable(node->element))
549 | {
550 | if (!_hcFileError(file, "No element before <%s> element.", buffer))
551 | return (false);
552 |
553 | node = hcNodeNewElement(*parent, HC_ELEMENT_TR);
554 | break;
555 | }
556 | }
557 | }
558 | else
559 | {
560 | // No new parent...
561 | node = NULL;
562 | }
563 |
564 | if (node)
565 | *parent = node;
566 |
567 | node = hcNodeNewElement(*parent, element);
568 |
569 | if (ch != '/' && !html_isleaf(element))
570 | *parent = node;
571 |
572 | while (ch != '>' && ch != EOF)
573 | {
574 | while ((ch = hcFileGetc(file)) != EOF)
575 | {
576 | if (!isspace(ch))
577 | break;
578 | }
579 |
580 | if (ch != '>')
581 | ch = html_parse_attr(file, ch, node);
582 | }
583 |
584 | return (ch == '>');
585 | }
586 |
587 |
588 | //
589 | // 'html_parse_unknown()' - Parse an unknown element or processing directive.
590 | //
591 |
592 | static bool // O - `true` to continue, `false` to stop
593 | html_parse_unknown(hc_file_t *file, // I - File to read from
594 | hc_node_t **parent, // IO - Parent node
595 | const char *unk) // I - Start of unknown markup
596 | {
597 | int ch; // Character from file
598 | char buffer[2048], // String buffer
599 | *bufptr, // Pointer into buffer
600 | *bufend; // End of buffer
601 |
602 |
603 | strncpy(buffer, unk, sizeof(buffer) - 1);
604 | buffer[sizeof(buffer) - 1] = '\0';
605 |
606 | bufptr = buffer + strlen(buffer);
607 | bufend = buffer + sizeof(buffer) - 1;
608 |
609 | while ((ch = hcFileGetc(file)) != EOF && ch != '>')
610 | {
611 | if (bufptr < bufend)
612 | *bufptr++ = (char)ch;
613 | else
614 | break;
615 |
616 | if (ch == '\'' || ch == '\"')
617 | {
618 | int quote = ch; // Quote character
619 |
620 | while ((ch = hcFileGetc(file)) != EOF && ch != quote)
621 | {
622 | if (bufptr < bufend)
623 | *bufptr++ = (char)ch;
624 | else
625 | break;
626 | }
627 |
628 | if (ch == EOF || ch != quote || bufptr >= bufend)
629 | break;
630 |
631 | *bufptr++ = (char)ch;
632 | }
633 | }
634 |
635 | *bufptr = '\0';
636 |
637 | if (ch == EOF)
638 | {
639 | _hcFileError(file, "Unexpected end-of-file.");
640 | return (false);
641 | }
642 | else if (ch != '>')
643 | _hcFileError(file, "Element too long.");
644 |
645 | return (_hcNodeNewUnknown(*parent, buffer) != NULL);
646 | }
647 |
--------------------------------------------------------------------------------